zlint

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 2, 2019 License: Apache-2.0 Imports: 6 Imported by: 9

README

ZLint

Build Status Go Report Card

ZLint is a X.509 certificate linter written in Go that checks for consistency with RFC 5280 and the CA/Browser Forum Baseline Requirements (v.1.4.8).

A detailed list of BR coverage can be found here: https://docs.google.com/spreadsheets/d/1ywp0op9mkTaggigpdF2YMTubepowJ50KQBhc_b00e-Y.

Requirements

ZLint requires Go 1.13.x or newer be installed. The command line setup instructions assume the go command is in your $PATH.

Versioning

ZLint aims to follow semantic versioning. The addition of new lints will generally result in a MINOR version revision. Since downstream projects depend on lint results and names for policy decisions changes of this nature will result in MAJOR version revision.

Command Line Usage

ZLint can be used on the command-line through a simple bundled executable ZLint as well as through ZCertificate, a more full-fledged command-line certificate parser that links against ZLint.

Example ZLint CLI usage:

go get github.com/zmap/zlint/cmd/zlint
zlint mycert.pem

Library Usage

ZLint can also be used as a library:

import (
	"github.com/zmap/zcrypto/x509"
	"github.com/zmap/zlint"
)

parsed, err := x509.ParseCertificate(raw)
if err != nil {
	// The certificate could not be parsed. Either error or halt.
	log.Fatalf("could not parse certificate: %s", err)
}
zlintResultSet := zlint.LintCertificate(parsed)

See https://github.com/zmap/zlint/blob/master/cmd/zlint/main.go for an example.

Adding New Lints

Generating Lint Scaffolding. The scaffolding for a new lints can be created by running ./newLint.sh <lint_name> <structName>. Lint names are generally of the form e_subject_common_name_not_from_san where the first letter is one of: e, w, or n (error, warning, or notice respectively). Struct names following Go conventions, e.g., subjectCommonNameNotFromSAN. Example: ./newLint.sh e_subject_common_name_not_from_san subjectCommonNameNotFromSAN. This will generate a new lint in the lints directory with the necessary fields filled out.

Choosing a Lint Result Level. When choosing what lints.LintStatus your new lint should return (e.g. Notice,Warn, Error, or Fatal) the following general guidance may help. Error should be used for clear violations of RFC/BR MUST or MUST NOT requirements and include strong citations. Warn should be used for violations of RFC/BR SHOULD or SHOULD NOT requirements and again should include strong citations. Notice should be used for more general "FYI" statements that violate non-codified community standards or for cases where citations are unclear. Lastly Fatal should be used when there is an unresolvable error in zlint, zcrypto or some other part of the certificate processing.

Scoping a Lint. Lints are executed in three steps. First, the ZLint framework determines whether a certificate falls within the scope of a given lint by calling CheckApplies. This is often used to scope lints to only check subscriber, intermediate CA, or root CAs. This function commonly calls one of a select number of helper functions: IsCA, IsSubscriber, IsExtInCert, or DNSNamesExist. Example:

func (l *caCRLSignNotSet) CheckApplies(c *x509.Certificate) bool {
	return c.IsCA && util.IsExtInCert(c, util.KeyUsageOID)
}

Next, the framework determines whether the certificate was issued after the effective date of a Lint by checking whether the certificate was issued prior to the lint's EffectiveDate. You'll also need to fill out the source and description of what the lint is checking. We encourage you to copy text directly from the BR or RFC here. Example:

func init() {
	RegisterLint(&Lint{
		Name:          "e_ca_country_name_missing",
		Description:   "Root and Subordinate CA certificates MUST have a countryName present in subject information",
		Citation:      "BRs: 7.1.2.1",
		EffectiveDate: util.CABEffectiveDate,
		Test:          &caCountryNameMissing{},
	})
}

The meat of the lint is contained within the RunTest function, which is passed x509.Certificate. Note: This is an X.509 object from ZCrypto not the Go standard library. Lints should perform their described test and then return a ResultStruct that contains a Result and optionally a Details string, e.g., ResultStruct{Result: Pass}. If you encounter a situation in which you typically would return a Go error object, instead return ResultStruct{Result: Fatal}.

Example:

func (l *caCRLSignNotSet) RunTest(c *x509.Certificate) *ResultStruct {
	if c.KeyUsage&x509.KeyUsageCRLSign != 0 {
		return &ResultStruct{Result: Pass}
	}
	return &ResultStruct{Result: Error}
}

Creating Unit Tests. Every lint should also have two corresponding unit tests for a success and failure condition. We have typically generated test certificates using Go (see https://golang.org/pkg/crypto/x509/#CreateCertificate for details), but OpenSSL could also be used. Test certificates should be placed in testlint/testCerts and called from the test file created by newLint.sh. Prepend the PEM with the output of openssl x509 -text.

Example:

func TestBasicConstNotCritical(t *testing.T) {
	// Only need to change these two values and the lint name
	inputPath := "../testlint/testCerts/caBasicConstNotCrit.pem"
	expected := Error
	out, _ := Lints["e_basic_constraints_not_critical"].ExecuteTest(ReadCertificate(inputPath))
	if out.Result != expected {
		t.Errorf("%s: expected %s, got %s", inputPath, expected, out.Status)
	}
}

Integration Tests. ZLint's continuous integration includes an integration test phase where all lints are run against a large corpus of certificates. The number of notice, warning, error and fatal results for each lint are captured and compared to a set of expected values in a configuration file. You may need to update these expected values when you add/change lints. Please see the integration tests README for more information.

Updating the TLD Map

ZLint maintains a map of top-level-domains and their validity periods that is referenced by linters. As ICANN adds and removes TLDs this map need to be updated. To do so, ensure the zlint-gtld-update command is installed and in your $PATH and run go generate:

go get github.com/zmap/zlint/cmd/zlint-gtld-update
go generate github.com/zmap/zlint/...

Zlint Users/Integrations

Pre-issuance linting is strongly recommended by the Mozilla root program. Here are some projects/CAs known to integrate with ZLint in some fashion:

Please submit a pull request to update the README if you are aware of another CA/project that uses zlint.

ZMap Copyright 2019 Regents of the University of Michigan

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See LICENSE for the specific language governing permissions and limitations under the License.

Documentation

Index

Constants

View Source
const Version int64 = 3

Variables

This section is empty.

Functions

func EncodeLintDescriptionsToJSON

func EncodeLintDescriptionsToJSON(w io.Writer)

EncodeLintDescriptionsToJSON outputs a description of each lint as JSON object, one object per line.

Types

type ResultSet

type ResultSet struct {
	Version         int64                        `json:"version"`
	Timestamp       int64                        `json:"timestamp"`
	Results         map[string]*lints.LintResult `json:"lints"`
	NoticesPresent  bool                         `json:"notices_present"`
	WarningsPresent bool                         `json:"warnings_present"`
	ErrorsPresent   bool                         `json:"errors_present"`
	FatalsPresent   bool                         `json:"fatals_present"`
}

ResultSet contains the output of running all lints against a single certificate.

func LintCertificate

func LintCertificate(c *x509.Certificate) *ResultSet

LintCertificate runs all registered lints on c, producing a ZLint.

func LintCertificateFiltered added in v1.1.0

func LintCertificateFiltered(c *x509.Certificate, filter *regexp.Regexp) *ResultSet

LintCertificateFiltered runs all lints with names matching the provided regexp on c, producing a ResultSet.

Directories

Path Synopsis
cmd
v3

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL