X.509 certificates
x509 reads an X.509 certificate: it decodes the DER bytes into typed fields (names, validity dates, public key, extensions) and verifies that one certificate's signature was made by a given issuer's key. It rejects unknown critical extensions. Chain building and the trust decision belong to the tls crate, not here.
Anything that speaks HTTPS eventually receives a certificate from the server: a blob of DER bytes claiming a name, an issuer, a public key, and a signature. In Node, JavaScript cousins of x509-parser and the platform's OpenSSL turn that blob into a decision: this name is who they say they are, this CA vouched for it, this signature holds. The decision is invisible from the outside; what shows up is a green lock.
Cruft makes that same decision with its own code. x509 is the crate that reads the blob and checks the signature, standing in for the crates.io x509-parser.
Alpha, not production. x509 (Cruft 0.0.10) has not been externally audited and has near-zero interop testing against real-world certificate corpora. Do not put it in front of adversarial input in production.
The reading layer and the chain policy
"Certificate validation" is at least two things, and x509 only owns the lower one.
- Reading the certificate: decode DER into typed fields (names, validity dates, public key, extensions), and check that one certificate's signature was produced by a given issuer's key.
- Deciding the chain: walk leaf -> intermediate -> trust anchor, enforce that each link is a CA allowed to sign the next, check validity windows against the clock, apply name constraints, and match the hostname.
x509 is the reading layer plus the single-hop signature check. The chain policy (which link may sign which, against your trust store, at the current time) lives in the tls crate. So "verifies the certificate" below means narrowly: it verifies that a signature over one cert's bytes was made by a specific public key. It does not, by itself, tell you the server is trustworthy.
What it reads
parse_certificate(der) decodes a Certificate through the sibling asn1-der crate. It surfaces, as typed accessors:
common_name()andsubject_alt_names(), the latter returning dNSName and iPAddressGeneralNames.Validity(notBefore/notAfter) and theSubjectPublicKeyInfo, which decodes to an RSA{n, e}or an EC{curve_oid, point}public key.- Typed extension accessors:
basic_constraints(),key_usage(),extended_key_usage(),name_constraints(), and a convenienceis_ca().
The typed extension decoders exist and are exercised. parse_certificate routes recognized extension OIDs (basicConstraints 2.5.29.19, keyUsage 2.5.29.15, extendedKeyUsage, nameConstraints 2.5.29.30) to their decoders, and the default test suite plus a tls consumer test cover a TLS caller reading cA / keyCertSign facts back out.
Critical extensions must be honored
An unknown field in a certificate is not ignored the way an unknown JSON key is. RFC 5280 section 4.2 says a certificate carrying an extension marked critical that the verifier does not recognize MUST be rejected: the issuer is asserting "you may not use this cert unless you honor this rule," and silently skipping it means honoring nothing.
x509 enforces this. reject_unknown_critical_extension fails parsing with UnknownCriticalExtension when an extension is critical and its OID is not in the recognized set (is_recognized_extension_oid). Unknown non-critical extensions are preserved as opaque Extension{oid, critical, value} and left alone.
"Checks the signature" means one specific hash-and-verify
verify_signature(cert, issuer_spki) is the whole of the cryptographic check this crate performs. It dispatches on the certificate's signatureAlgorithm OID:
- RSA PKCS#1 v1.5 with SHA-256/384/512: hashes the
tbsCertificatebytes and callsrusty_web_crypto::rsa_pkcs1_v15_verify. - ECDSA with SHA-256/384/512 over P-256 or P-384: unpacks the uncompressed point, decodes the
SEQUENCE { INTEGER r, INTEGER s }signature, and callsrusty_web_crypto::ecdsa_verify.
Anything else returns UnsupportedSigAlg. SHA-1 is deliberately not in the match arms.
This path runs in a default build without OpenSSL keygen. Checked-in deterministic P-256 fixtures drive four tests: a self-signed cert verifies, a corrupted signature is rejected, a wrong-issuer key is rejected, and a malformed ECDSA signature encoding is rejected. A tls chain-walk smoke test consumes the same fixture and rejects a tampered trust anchor.
What it does not do
- No chain building, no trust-anchor decision, no clock check.
verify_signaturetakes the issuer's key as an argument; it does not find the issuer, does not consult a trust store, and does not comparenotAfterto now. Deciding the chain istls's job.x509givestlsthe facts (is_ca, key usage, name constraints, the single-hop signature verdict); it does not apply the policy. - No revocation. No CRL, no OCSP. A revoked-but-unexpired certificate reads as valid here.
- A narrow algorithm set. RSA PKCS#1 v1.5 and ECDSA P-256/P-384 only. No RSA-PSS, no Ed25519, no P-521. Real-world certs outside that set return
UnsupportedSigAlg. - Broad malformed-input hardening is not yet proven. The default fixture tests cover the happy path and a handful of rejections. There is no fuzzing target over
parse_certificateand no differential lane againstx509-parseror OpenSSL over a large hostile corpus. The memory-safety and canonical-DER properties are inspection-strength, not fuzz-strength.
Hardening that is in place
Several security-relevant weaknesses that a parser like this tends to ship with have been closed:
- Typed extensions and the critical bit. The parser once exposed no typed extension accessors and never checked the
criticalbit, which was the root-cause layer under the TLS "any leaf can act as issuer" hole. It now decodes BasicConstraints / KeyUsage / ExtendedKeyUsage / NameConstraints, exposes the accessors andis_ca(), and rejects unknown critical extensions at parse time. The downstream chain enforcement is owned bytls. - Name decoding. Embedded NULs in DN attributes and SAN dNSName values (the classic CN-null confusion) are rejected; BMPString/UniversalString decode through one checked path; TeletexString is rejected rather than mis-decoded as UTF-8; iPAddress is length-validated to exactly IPv4 or IPv6.
- SHA-1 signatures.
sha1WithRSAEncryptionis removed from the default verifier dispatch, and inner/outer signatureAlgorithm equality is enforced. SHA-1 certs still parse as structure but returnUnsupportedSigAlginstead of being hashed and verified under a collision-broken hash. - Default test coverage. The verifier was once exercised only under an ignored test with OpenSSL present. The deterministic P-256 fixtures above now run in a default build.
Limitations
x509 is a competent single-hop certificate reader and signature checker with a running default test in place. It is not yet a validated, interop-proven, fuzz-hardened X.509 stack. x509-parser (plus the platform OpenSSL it typically sits beside) is the maturity baseline, and that baseline leads on exactly the axes Cruft cannot yet claim: years of exposure to real and hostile certificates, a broad algorithm set, and battle-tested DER hardening. The security-relevant weaknesses are closed and enforced, but that is not the same as an external audit. It reads certificates and verifies single-hop signatures, on a small deterministic fixture set, unverified against the adversarial long tail. Do not stand production trust decisions on it.
And keep the layering straight: even fully correct, this crate does not decide a server is trustworthy. It hands facts to tls, which owns the chain policy.