X509
Introduction
import 'x509';The x509 module parses X.509 certificates and verifies certificate chains. A verification builds a chain from the server certificate to a trusted root certificate, verifies the signature on each certificate in the chain, and makes sure that the server certificate is valid for the host name. Notes gives the full list of checks.
tls.connect does this verification automatically. Most programs do not import this module. A program imports it to:
- read the fields of a certificate;
- trust a private certificate authority;
- verify a chain that did not come from
tls.connect.
import 'x509';
import 'tls';
import 'time';
import 'string';
void async inspect() {
tls.Conn c = await tls.connect("example.com:443", "example.com", null) catch e { return; };
x509.Certificate leaf = x509.parseCertificate(c.peerCertificates[0]) catch e { return; };
print(leaf.subject); // CN=example.com
print(string.join(leaf.dnsNames, ", ")); // example.com, *.example.com
print(time.date.toText(leaf.notAfter)); // 2026-10-27T22:17:20Z
await c.close();
}Notes
A chain verification checks all of these conditions:
- each certificate, from the server’s certificate up to the root, is signed by the certificate above it;
- each certificate is within its validity dates;
- each certificate that signs another certificate is permitted to sign, and the chain is not longer than the signers permit;
- each certificate is within the names that its signers restrict it to (see Name constraints);
- the server’s certificate is valid for the requested host name;
- no certificate contains a required (“critical”) extension that this module does not support.
Signatures made with RSA (SHA-256 or SHA-384) and ECDSA (on the P-256 and P-384 curves) are supported. One chain can contain both kinds. All other signatures are refused, including signatures that use SHA-1 or MD5.
Revocation is not checked. A certificate that its authority revoked before its expiry date is still accepted. Use these protections instead:
- Short certificate lifetimes reduce the time during which a revoked certificate is usable.
- Narrow trust with
tls.Options.rootsortls.Options.pins.
This module checks certificates. It cannot create or sign them. The standard library has no signing functions and no TLS server.
A failure has an x509.ErrorCode that identifies the problem. tls.connect and http.request pass these codes through unchanged:
| Code | Meaning |
|---|---|
BAD_CERTIFICATE | The bytes are not a valid certificate. |
UNSUPPORTED_ALGORITHM | A signature or key that this module cannot check. |
EXPIRED | The certificate’s end date has passed. |
NOT_YET_VALID | The certificate’s start date is in the future. |
UNTRUSTED_ROOT | No chain leads to a trusted authority. |
HOSTNAME_MISMATCH | The certificate is valid, but for a different server. |
CONSTRAINT_VIOLATION | A certificate was used for a purpose that it does not permit. |
BAD_SIGNATURE | A signature does not match. |
The module is strict because it reads data from untrusted sources. It refuses a malformed or unusually encoded certificate. Every size and every search has a limit. As a result, a malicious certificate cannot crash the program or use unlimited processing time.
x509.parseCertificate()
x509.Certificate x509.parseCertificate(byte[] data)Reads one certificate in DER form, which is the binary form. The text form with -----BEGIN CERTIFICATE----- lines goes through x509.parsePem() first. It fails with BAD_CERTIFICATE if the data is not a valid certificate.
The result has these fields:
| Field | Type | Description |
|---|---|---|
subject, issuer | String | The entity that the certificate is for, and the entity that signed it. |
serialNumber | String | The serial number, in lower-case hex. |
notBefore, notAfter | DateTime | The start and the end of the validity period. |
dnsNames, ipAddresses, emailAddresses | String[] | The names that it is valid for. |
isCA, maxPathLen | bool, int | Whether it can sign other certificates, and the maximum length of a chain below it (-1 for no limit). |
keyUsage | int | The permitted uses of its key. cert.hasKeyUsage(bit) tests it. |
extKeyUsage | String[] | More permitted uses, as identifiers such as "1.3.6.1.5.5.7.3.1" (server authentication). |
signatureAlgorithm, publicKeyAlgorithm | String | Identifiers for the signature algorithm and for the type of key. |
rsaModulus, rsaExponent | byte[], int | The key, if it is an RSA key. |
ecCurve, ecPoint | String, byte[] | The key, if it is an ECDSA key: "p256" or "p384", and the point. |
extensions | x509.Extension[] | All extensions, including extensions that this module does not interpret. |
unhandledCritical | String[] | Required extensions that this module does not support. |
nameConstraintsPresent, permittedSubtrees, excludedSubtrees, unsupportedConstraints | The name restrictions that it applies to certificates below it. Name constraints describes them. | |
raw, rawTbs, rawIssuer, rawSubject, rawSpki | byte[] | The original bytes of the certificate and of its parts. |
import 'x509';
import 'fs';
void describe(String file) {
x509.Certificate cert = x509.parseCertificate(fs.readFile(file)) catch e {
print("not a certificate: " + e.message);
return;
};
print(cert.subject);
print(cert.isCA);
}x509.verify()
x509.Certificate[] x509.verify(x509.Certificate[] chain, x509.Options? options)Verifies a chain of parsed certificates, with the server’s own certificate first. It returns the chain that it built, which ends at the trusted authority that signed the chain. x509.verifyChain() does the same from the raw certificates.
The module uses the certificates that a server sends only as candidates. The returned chain is the chain that this module built and verified. It can be different from the chain that was sent.
x509.Options holds three settings. All are optional:
| Field | Type | When left out |
|---|---|---|
dnsName | String? | The host name is not checked. A client must always set it. |
roots | x509.Certificate[]? | The module uses the authorities that the system trusts (x509.systemRoots()). |
at | DateTime? | Validity is checked at the current time. |
import 'x509';
void check(x509.Certificate[] sent) {
x509.Certificate[] chain = x509.verify(sent, { dnsName: "example.com" }) catch e {
print("not trusted: " + e.message);
return;
};
print(chain[chain.length - 1].subject); // the authority that vouches for it
}x509.verifyChain()
x509.Certificate[] x509.verifyChain(byte[][] ders, String? dnsName,
x509.Certificate[]? roots, DateTime? at)Does all steps in one call. It reads each raw certificate and verifies the chain as x509.verify() does. The tls module uses this function. It returns the chain that it built, which can be different from the chain that was sent. dnsName, roots and at have the same meaning as the fields of x509.Options.
import 'x509';
void check(byte[][] sent) {
x509.Certificate[] chain = x509.verifyChain(sent, "example.com", null, null) catch e {
print("not trusted: " + e.message);
return;
};
print(chain[0].subject); // the server's certificate
print(chain[chain.length - 1].subject); // the authority that vouches for it
}x509.matchHostname()
void x509.matchHostname(x509.Certificate cert, String host)Checks that cert is valid for host. It fails with HOSTNAME_MISMATCH if it is not. Only the certificate’s list of names (dnsNames and ipAddresses) is used. The “common name” in the subject is ignored.
Upper and lower case are equal, and a trailing dot is permitted. Wildcards have these limits:
| Name in the certificate | Matches | Does not match |
|---|---|---|
*.example.com | www.example.com | example.com, a.b.example.com |
*.com | nothing: too broad | |
x*.example.com | nothing: a wildcard must be a whole label |
An IP address matches only the certificate’s ipAddresses, never a wildcard name.
Name constraints
An authority’s certificate can restrict the names that the certificates below it can use. For example, a company authority can be permitted to issue certificates only under internal.example.com. This module enforces these restrictions for the three kinds of name that it reads:
| Kind | A restriction covers |
|---|---|
| Host names | the name and all names below it, compared label by label. For example, notexample.com is not inside example.com. |
| Email addresses | one complete address; or a host, which means addresses at that host only; or a domain with a leading dot, which means addresses at hosts below it |
| IP addresses | addresses in the given range |
These four rules apply:
- Exclusions take priority. A name inside an excluded range is refused, even if the permitted list includes it.
- A restriction applies only to its own kind of name. Email restrictions do not apply to a certificate that has no email addresses.
- An authority that re-issues its own certificate is not held to the new restriction in that certificate. As a result, an authority can add a restriction.
- A restriction that this module cannot check blocks the authority completely. It is listed in
cert.unsupportedConstraints. No certificate that the authority signed is accepted, because the module cannot show that the certificate is permitted.
A program can also read the restrictions:
import 'x509';
void showLimits(x509.Certificate ca) {
if (ca.nameConstraintsPresent) {
forEach(ca.permittedSubtrees, sub) {
if (sub.kind == x509.GN_DNS) {
print("permits " + sub.name);
}
}
}
}x509.spkiPin()
byte[] x509.spkiPin(x509.Certificate cert)A pin is the SHA-256 hash of a certificate’s public key. The pin covers only the key. As a result, a certificate that is renewed with the same key keeps its pin. spkiPin calculates a pin. It returns the same 32 bytes as this openssl command:
openssl x509 -in server.der -inform DER -pubkey -noout \
| openssl pkey -pubin -outform DER | openssl dgst -sha256import 'x509';
import 'crypto';
void showPin(x509.Certificate cert) {
print(crypto.hexEncode(x509.spkiPin(cert))); // the same hex openssl prints
}x509.pinned()
bool x509.pinned(x509.Certificate[] chain, byte[][] pins)Returns true if a certificate in the chain has one of the pinned keys (see x509.spkiPin()). Its result has a meaning only for a chain that is already verified. A pin limits which trusted chains are accepted. It does not make a chain trusted. An empty list of pins matches nothing.
import 'x509';
void checkPin(byte[][] sent, byte[][] myPins) {
x509.Certificate[] chain = x509.verifyChain(sent, "example.com", null, null) catch e { return; };
if (!x509.pinned(chain, myPins)) {
print("trusted, but the key is not pinned");
}
}For connections, tls.Options.pins does this check during the connection.
x509.parsePem()
byte[][] x509.parsePem(String text)Reads all certificates from a PEM file and returns the bytes of each certificate, for use with parseCertificate. PEM is the text form, with -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- lines. Text between the blocks is skipped. Blocks of other kinds, such as keys, are also skipped. A block that has no end line, or whose contents are not valid base64, causes an error.
import 'x509';
import 'fs';
import 'string';
void loadBundle(String file) {
byte[][] ders = x509.parsePem(string.fromByteArray(fs.readFile(file) catch [])) catch e { return; };
print(ders.length);
}x509.systemRoots()
x509.Certificate[] x509.systemRoots()Returns the certificate authorities that the system trusts. It reads them from the usual locations on macOS and Linux, such as /etc/ssl/cert.pem. It fails instead of returning an empty list, because with an empty list every chain would be refused and the cause would not be reported. It does not work on Windows, because Windows makes its trusted authorities available only through a system interface, not in a file.
If a certificate in the system file cannot be read, it is skipped and the other certificates are loaded.
x509.checkSignature()
void x509.checkSignature(x509.Certificate child, x509.Certificate parent)Checks that child was signed with the key of parent. It fails with BAD_SIGNATURE if it was not, and with UNSUPPORTED_ALGORITHM if this module cannot check the type of signature. verify does this check for each link of a chain. This function checks one link.
x509.rsaPublicKey()
x509.RsaPublicKey x509.rsaPublicKey(x509.Certificate cert)rsaPublicKey, x509.ecdsaPublicKey() and x509.parseEcdsaSignature() are for signatures other than the certificate’s own signature, for example the signature that a server makes during a TLS handshake.
rsaPublicKey returns a certificate’s RSA key, as the fields modulus and exponent, in the form that the crypto verification functions take. It fails if the certificate has the other kind of key.
import 'x509';
import 'crypto';
void checkSigned(x509.Certificate cert, byte[] message, byte[] signature) {
x509.RsaPublicKey key = x509.rsaPublicKey(cert) catch e { return; };
crypto.rsaVerifyPss(key.modulus, key.exponent, "sha256", crypto.sha256(message), signature)
catch e {
print("not signed by that key");
return;
};
print("signed");
}x509.ecdsaPublicKey()
x509.EcdsaPublicKey x509.ecdsaPublicKey(x509.Certificate cert)Returns a certificate’s ECDSA key, as the fields curve and point, in the form that crypto.ecdsaVerify takes. It fails if the certificate has the other kind of key. As with x509.rsaPublicKey(), it is for a signature other than the certificate’s own signature. The example under x509.parseEcdsaSignature() uses it.
x509.parseEcdsaSignature()
x509.EcdsaSignature x509.parseEcdsaSignature(byte[] data)Splits an encoded ECDSA signature into the two integers that crypto.ecdsaVerify expects, as the fields r and s.
import 'x509';
import 'crypto';
void checkSigned(x509.Certificate cert, byte[] message, byte[] signature) {
x509.EcdsaPublicKey key = x509.ecdsaPublicKey(cert) catch e { return; };
x509.EcdsaSignature sig = x509.parseEcdsaSignature(signature) catch e { return; };
crypto.ecdsaVerify(key.curve, key.point, crypto.sha256(message), sig.r, sig.s) catch e {
print("not signed by that key");
return;
};
print("signed");
}