Skip to content

Pre-issuance certificate linting support. #1008

@cpu

Description

@cpu

Hi folks,

At Let's Encrypt we would like to build optional pre-issuance certificate linting into CFSSL. Pre-issuance linting is repeatedly cited as a best-practice in incident post-mortems in the web PKI community. Adding support to CFSSL will help interested parties add more safe-guards to their issuance pipeline. I'd be happy to commit to doing the work provided the proposed design outlined below sounds good.

Boulder already uses zlint, a Go based X.509 Certificate Linter, as a library for post-issuance linting. I think it makes sense to integrate this project as a CFSSL dependency to accomplish pre-issuance linting.

High Level

At a high level we would like to:

  • Add a dependency on zlint
  • Update the config.SigningProfile struct to accept two new optional fields:
    1. lintErrLevel int
    2. ignoredLints []string
  • Update the signer.local.Signer struct to generate one throw-away private key at creation for linting.
  • Update the signer.local.Signer's sign(*x509.Certificate) function to:
    1. Sign the TBSCert template with the Signer's throw-away key.
    2. Pass the the resulting cert to zlint for linting.
    3. Handle any lint results above the lintErrLevel as errors (iff the lint is not in the ignoredLints list)

Adding a dep.

We think integrating zlint directly so it can work out-of-box with a config change rather than building in a callback for the same purpose and supplying the linting externally on a project-specific basis will help more folks benefit from the linting. zlint has a bias towards CA/B reqs and RFC 5280 but it is an extensible way to add additional lints and many of the existing lints vet best-practices that are broadly applicable.

If it helps you feel better about a new dependency I'm also a committer/maintainer of zlint so you would have a contact on that side of the fence from the get-go.

Config changes

The lintErrLevel will default to 0 for existing configs. We'll match this up with the zlint.LintResult.LintStatus in the local signer to know when to treat a result as an error. Any LintStatus > the lintErrLevel will be an error, except in the case where lintErrLevel is zero and linting is disabled.

  • 0 = zlint.lints.Reserved -> no linting will be done. no results considered. Existing CFSSL behaviour.
  • 1, 2 = zlint.lints.NA and zlint.lints.NE -> these don't really make sense in this context.
  • 3 = zlint.lints.Pass -> all lint results except pass will be considered errors.
  • 4 = zlint.lints.Notice -> all lint results except pass and notice will be considered errors.
  • 5 = zlint.lints.Warn -> all lint results except pass, notice and warning will be considered errors.
  • 6 = zlint.lints.Error -> all lint results except pass, notice, warning, and error will be considered errors (e.g. fatal errors only)
  • 7+ = zlint.lints.Fatal -> no errors will be treated as errors.

The ignoredLints map offers a more fine-grained way to ignore specific lints by name. E.g.:

ignoredLints: [
  "n_subject_common_name_included",
  "subject_contains_reserved_arpa_ip"
]

What do you folks think? CC @cbroglie

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions