Skip to main content

Crate pkix_lint

Crate pkix_lint 

Source
Expand description

Lint engine for X.509 certificate chains — structured soft-fail and advisory results.

§What this crate provides

pkix-path returns Result<ValidatedPath, Error> — hard pass or fail. That model cannot express “this certificate is RFC 5280 valid but violates CA/B Forum BR §7.1.4.2” without aborting the chain entirely.

pkix-lint adds an advisory layer:

  • Lint — the unit of evaluation. Each lint has a stable ID, a normative citation, a severity, a scope (certificate vs. full chain path), and a subject-kind filter (leaf, intermediate CA, etc.).
  • LintResultPass | NotApplicable | Warn | Error | Fatal. Warn and Error carry a &'static str detail message. Fatal within pkix-lint means “stop evaluating further lints” — it is not a TLS hard-fail. See the advisory-only contract below.
  • Finding — a lint ID paired with a LintResult, optionally referencing the chain index of the offending certificate.
  • LintRunner — evaluates a slice of dyn Lint objects against a certificate or validated path and returns Vec<Finding>.
  • LintProfile — extends pkix_path::Profile with a lints() method so that a profile can bundle its own lint set.

§Finding ID stability

Finding IDs (returned by Lint::id) are part of the public API. They MUST NOT change between crate versions without a semver-major bump. Format convention: <regime>.<section>.<noun>, e.g.:

  • "cabf.br.tls.validity.max"
  • "cabf.smime.san.type"
  • "rfc5280.basic_constraints.ca_flag"

§Advisory-only contract

pkix-lint findings never cause a certificate to be rejected. All runner methods return Vec<Finding> — they never return Result::Err and they never cause a TLS stack to abort a connection. Findings are advisory signals.

Whether to act on a finding (reject a TLS connection, block a cert, alert an operator) is the caller’s decision, configured per finding-ID at the integration layer (e.g., pkix-chain or a TLS stack binding). This design is intentional:

  • pkix-lint does not know whether you are in audit, monitoring, or enforcement context. The caller does.
  • Spec ambiguity (CA/B Forum CPs, FPKI CPs, etc.) means some findings require human judgment before enforcement. Hard-fail by default would cause outages.
  • The deviation/waiver mechanism (PKIX-jge) operates at this layer, not in pkix-lint core.

The only in-engine effect of LintResult::Fatal is stopping further lint evaluation for the current item — it does not escape as an error.

§Design rationale

Inspired by zlint and certlint but with several deliberate differences:

  • Trait-based, not enum-based: external crates can implement Lint and pass Box<dyn Lint> to LintRunner without modifying this crate.
  • Cow detail messages: LintResult::Warn, Error, and Fatal carry Cow<'static, str> detail. Static string literals are zero-allocation (Cow::Borrowed); runtime-formatted strings such as format!(...) use Cow::Owned without leaking memory.
  • Temporality-aware: LintRunner::run_cert takes now_unix: u64 so lints can enforce rules that have effective dates (e.g., SC-081 validity caps).
  • Scope-separated: certificate lints and path lints run in separate passes so path lints can see the full validated output.

§Example

// `cert` and `now_unix` are obtained from the calling context (e.g., loaded
// from DER and current wall-clock time). They are not defined here so the
// example cannot be run in a doctest harness without external fixtures.
use pkix_lint::{Lint, LintResult, LintRunner, Scope, Severity, SubjectKind};
use x509_cert::Certificate;

#[derive(Clone)]
struct MyLint;
impl Lint for MyLint {
    fn id(&self) -> &'static str { "example.my_lint" }
    fn citation(&self) -> &'static str { "Example Corp Policy §1.2" }
    fn severity(&self) -> Severity { Severity::Warn }
    fn scope(&self) -> Scope { Scope::Certificate }
    fn applies_to(&self) -> SubjectKind { SubjectKind::Leaf }
    fn check_cert(&self, cert: &Certificate, _kind: SubjectKind, _now_unix: u64) -> LintResult {
        if cert.tbs_certificate.subject.to_string().is_empty() {
            LintResult::warn("empty Subject DN")
        } else {
            LintResult::Pass
        }
    }
}

let cert: Certificate = unimplemented!("load from DER");
let now_unix: u64 = unimplemented!("current Unix epoch seconds");
let runner = LintRunner::new(vec![Box::new(MyLint)]);
let findings = runner.run_cert(&cert, SubjectKind::Leaf, 0, now_unix);
for f in &findings {
    println!("{}: {:?}", f.lint_id, f.result);
}

§Limitations

  • Framework, not a comprehensive rule set. This crate ships the Lint trait, LintRunner, and a small RFC-conformance lint set in the rfc5280, rfc6125, rfc8398, and rfc8551 modules. Comprehensive industry-forum lint coverage is the job of policy adapter crates (pkix-policy-zlint for zlint’s ~700 rules, pkix-policy-pkilint for pkilint’s S/MIME BR + ETSI coverage). CA/B Forum reference lints live in the sibling pkix-lint-cabf crate; that crate is also explicitly small and curated.
  • Advisory-only. Findings never cause a TLS rejection by themselves (see the contract above). Plumbing findings into hard-fail or waiver decisions is the integration layer’s job.
  • OSCAL adapter is one supported output, not the canonical format. The oscal feature emits OSCAL Assessment Results JSON and parses OSCAL Risk-based deviations back into deviation::DeviationStore. The workspace does not prescribe OSCAL as a canonical inter-tool wire format (AGENTS.md non-negotiable #5, three-mode policy architecture); each policy-adapter crate consumes its upstream tool’s natural format.
  • No site-local policy DSL. Site-local policy is the deployer’s responsibility; implement Lint (or load lints from any deployer-chosen format) and feed LintRunner.

Modules§

deviation
Deviation (waiver) mechanism for pkix-lint.
oscaloscal
NIST OSCAL bridge for pkix-lint outputs.
report
Evidence pack: EvaluationReport bundles all findings from a lint run.
rfc5280
RFC 5280 conformance lints.
rfc6125
RFC 6125 conformance lints.
rfc8398
RFC 8398 conformance lints.
rfc8551
RFC 8551 conformance lints.

Structs§

Finding
A recorded lint outcome, associating a lint ID with its result.
LintParameter
Descriptor for a tunable parameter exposed by a Lint implementation.
LintRunner
Evaluates a collection of Lints against certificates or a validated path.
ValidatedPath
The result of a successful certificate path validation.
ValidationPolicy
Policy parameters controlling path validation.

Enums§

LintResult
The outcome of evaluating a single lint against a certificate or path.
ParameterError
Error reported by Lint::set_parameter.
Scope
Whether a lint evaluates a single certificate or the complete validated path.
Severity
How seriously to treat a lint finding.
SubjectKind
Which certificate positions in the chain a lint applies to.

Traits§

Lint
A single, independently evaluable lint check.
LintClone
Supertrait of Lint that lets Box<dyn Lint> be cloned.
LintProfile
A Profile that also bundles a set of lints.
Profile
A PKI regime profile that bundles identity, citation, and a validation policy.

Functions§

check_shape
Run the certificate-scope lints of profile against cert and report pass/fail without walking a chain or verifying signatures.