Skip to content

Commit e49e811

Browse files
authored
Merge branch 'main' into fix-crl-observer
2 parents fe98b20 + 6071bed commit e49e811

File tree

7 files changed

+157
-71
lines changed

7 files changed

+157
-71
lines changed

issuance/crl.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import (
1717
type CRLProfileConfig struct {
1818
ValidityInterval config.Duration
1919
MaxBackdate config.Duration
20+
21+
// LintConfig is a path to a zlint config file, which can be used to control
22+
// the behavior of zlint's "customizable lints".
23+
LintConfig string
24+
// IgnoredLints is a list of lint names that we know will fail for this
25+
// profile, and which we know it is safe to ignore.
26+
IgnoredLints []string
2027
}
2128

2229
type CRLProfile struct {
@@ -38,10 +45,17 @@ func NewCRLProfile(config CRLProfileConfig) (*CRLProfile, error) {
3845
return nil, fmt.Errorf("crl max backdate must be non-negative, got %q", config.MaxBackdate)
3946
}
4047

41-
reg, err := linter.NewRegistry(nil)
48+
reg, err := linter.NewRegistry(config.IgnoredLints)
4249
if err != nil {
4350
return nil, fmt.Errorf("creating lint registry: %w", err)
4451
}
52+
if config.LintConfig != "" {
53+
lintconfig, err := lint.NewConfigFromFile(config.LintConfig)
54+
if err != nil {
55+
return nil, fmt.Errorf("loading zlint config file: %w", err)
56+
}
57+
reg.SetConfiguration(lintconfig)
58+
}
4559

4660
return &CRLProfile{
4761
validityInterval: config.ValidityInterval.Duration,

linter/lints/rfc/lint_cert_via_pkimetal.go

+76-68
Original file line numberDiff line numberDiff line change
@@ -17,115 +17,80 @@ import (
1717
"github.com/zmap/zlint/v3/util"
1818
)
1919

20-
type certViaPKIMetal struct {
20+
// PKIMetalConfig and its execute method provide a shared basis for linting
21+
// both certs and CRLs using PKIMetal.
22+
type PKIMetalConfig struct {
2123
Addr string `toml:"addr" comment:"The address where a pkilint REST API can be reached."`
2224
Severity string `toml:"severity" comment:"The minimum severity of findings to report (meta, debug, info, notice, warning, error, bug, or fatal)."`
2325
Timeout time.Duration `toml:"timeout" comment:"How long, in nanoseconds, to wait before giving up."`
2426
IgnoreLints []string `toml:"ignore_lints" comment:"The unique Validator:Code IDs of lint findings which should be ignored."`
2527
}
2628

27-
func init() {
28-
lint.RegisterCertificateLint(&lint.CertificateLint{
29-
LintMetadata: lint.LintMetadata{
30-
Name: "e_pkimetal_lint_cabf_serverauth_cert",
31-
Description: "Runs pkimetal's suite of cabf serverauth certificate lints",
32-
Citation: "https://github.com/pkimetal/pkimetal",
33-
Source: lint.Community,
34-
EffectiveDate: util.CABEffectiveDate,
35-
},
36-
Lint: NewCertViaPKIMetal,
37-
})
38-
}
39-
40-
func NewCertViaPKIMetal() lint.CertificateLintInterface {
41-
return &certViaPKIMetal{}
42-
}
43-
44-
func (l *certViaPKIMetal) Configure() any {
45-
return l
46-
}
47-
48-
func (l *certViaPKIMetal) CheckApplies(c *x509.Certificate) bool {
49-
// This lint applies to all certificates issued by Boulder, as long as it has
50-
// been configured with an address to reach out to. If not, skip it.
51-
return l.Addr != ""
52-
}
53-
54-
// finding matches the repeated portion of PKIMetal's documented JSON response.
55-
// https://github.com/pkimetal/pkimetal/blob/578ac224a7ca3775af51b47fce16c95753d9ac8d/doc/openapi.yaml#L201-L221
56-
type finding struct {
57-
Linter string `json:"linter"`
58-
Finding string `json:"finding"`
59-
Severity string `json:"severity"`
60-
Code string `json:"code"`
61-
Field string `json:"field"`
62-
}
63-
64-
func (l *certViaPKIMetal) Execute(c *x509.Certificate) *lint.LintResult {
65-
timeout := l.Timeout
29+
func (pkim *PKIMetalConfig) execute(endpoint string, der []byte) (*lint.LintResult, error) {
30+
timeout := pkim.Timeout
6631
if timeout == 0 {
6732
timeout = 100 * time.Millisecond
6833
}
6934

7035
ctx, cancel := context.WithTimeout(context.Background(), timeout)
7136
defer cancel()
7237

38+
apiURL, err := url.JoinPath(pkim.Addr, endpoint)
39+
if err != nil {
40+
return nil, fmt.Errorf("constructing pkimetal url: %w", err)
41+
}
42+
7343
// reqForm matches PKIMetal's documented form-urlencoded request format. It
74-
// does not include the "format" or "profile" fields, as their default values
75-
// ("json" and "autodetect", respectively) are good for our purposes.
44+
// does not include the "profile" field, as its default value ("autodetect")
45+
// is good for our purposes.
7646
// https://github.com/pkimetal/pkimetal/blob/578ac224a7ca3775af51b47fce16c95753d9ac8d/doc/openapi.yaml#L179-L194
7747
reqForm := url.Values{}
78-
reqForm.Set("b64input", base64.StdEncoding.EncodeToString(c.Raw))
79-
reqForm.Set("severity", l.Severity)
48+
reqForm.Set("b64input", base64.StdEncoding.EncodeToString(der))
49+
reqForm.Set("severity", pkim.Severity)
50+
reqForm.Set("format", "json")
8051

81-
url := fmt.Sprintf("%s/lintcert", l.Addr)
82-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(reqForm.Encode()))
52+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, strings.NewReader(reqForm.Encode()))
8353
if err != nil {
84-
return &lint.LintResult{
85-
Status: lint.Error,
86-
Details: fmt.Sprintf("creating pkimetal request: %s", err),
87-
}
54+
return nil, fmt.Errorf("creating pkimetal request: %w", err)
8855
}
8956
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
9057
req.Header.Add("Accept", "application/json")
9158

9259
resp, err := http.DefaultClient.Do(req)
9360
if err != nil {
94-
return &lint.LintResult{
95-
Status: lint.Error,
96-
Details: fmt.Sprintf("making POST request to pkimetal API: %s (timeout %s)", err, timeout),
97-
}
61+
return nil, fmt.Errorf("making POST request to pkimetal API: %s (timeout %s)", err, timeout)
9862
}
9963
defer resp.Body.Close()
10064

10165
if resp.StatusCode != http.StatusOK {
102-
return &lint.LintResult{
103-
Status: lint.Error,
104-
Details: fmt.Sprintf("got status %d (%s) from pkimetal API", resp.StatusCode, resp.Status),
105-
}
66+
return nil, fmt.Errorf("got status %d (%s) from pkimetal API", resp.StatusCode, resp.Status)
10667
}
10768

10869
resJSON, err := io.ReadAll(resp.Body)
10970
if err != nil {
110-
return &lint.LintResult{
111-
Status: lint.Error,
112-
Details: fmt.Sprintf("reading response from pkimetal API: %s", err),
113-
}
71+
return nil, fmt.Errorf("reading response from pkimetal API: %s", err)
72+
}
73+
74+
// finding matches the repeated portion of PKIMetal's documented JSON response.
75+
// https://github.com/pkimetal/pkimetal/blob/578ac224a7ca3775af51b47fce16c95753d9ac8d/doc/openapi.yaml#L201-L221
76+
type finding struct {
77+
Linter string `json:"linter"`
78+
Finding string `json:"finding"`
79+
Severity string `json:"severity"`
80+
Code string `json:"code"`
81+
Field string `json:"field"`
11482
}
11583

11684
var res []finding
11785
err = json.Unmarshal(resJSON, &res)
11886
if err != nil {
119-
return &lint.LintResult{
120-
Status: lint.Error,
121-
Details: fmt.Sprintf("parsing response from pkimetal API: %s", err),
122-
}
87+
return nil, fmt.Errorf("parsing response from pkimetal API: %s", err)
12388
}
12489

12590
var findings []string
12691
for _, finding := range res {
12792
id := fmt.Sprintf("%s:%s", finding.Linter, finding.Code)
128-
if slices.Contains(l.IgnoreLints, id) {
93+
if slices.Contains(pkim.IgnoreLints, id) {
12994
continue
13095
}
13196
desc := fmt.Sprintf("%s from %s at %s", finding.Severity, id, finding.Field)
@@ -141,8 +106,51 @@ func (l *certViaPKIMetal) Execute(c *x509.Certificate) *lint.LintResult {
141106
return &lint.LintResult{
142107
Status: lint.Error,
143108
Details: fmt.Sprintf("got %d lint findings from pkimetal API: %s", len(findings), strings.Join(findings, "; ")),
109+
}, nil
110+
}
111+
112+
return &lint.LintResult{Status: lint.Pass}, nil
113+
}
114+
115+
type certViaPKIMetal struct {
116+
PKIMetalConfig
117+
}
118+
119+
func init() {
120+
lint.RegisterCertificateLint(&lint.CertificateLint{
121+
LintMetadata: lint.LintMetadata{
122+
Name: "e_pkimetal_lint_cabf_serverauth_cert",
123+
Description: "Runs pkimetal's suite of cabf serverauth certificate lints",
124+
Citation: "https://github.com/pkimetal/pkimetal",
125+
Source: lint.Community,
126+
EffectiveDate: util.CABEffectiveDate,
127+
},
128+
Lint: NewCertViaPKIMetal,
129+
})
130+
}
131+
132+
func NewCertViaPKIMetal() lint.CertificateLintInterface {
133+
return &certViaPKIMetal{}
134+
}
135+
136+
func (l *certViaPKIMetal) Configure() any {
137+
return l
138+
}
139+
140+
func (l *certViaPKIMetal) CheckApplies(c *x509.Certificate) bool {
141+
// This lint applies to all certificates issued by Boulder, as long as it has
142+
// been configured with an address to reach out to. If not, skip it.
143+
return l.Addr != ""
144+
}
145+
146+
func (l *certViaPKIMetal) Execute(c *x509.Certificate) *lint.LintResult {
147+
res, err := l.execute("lintcert", c.Raw)
148+
if err != nil {
149+
return &lint.LintResult{
150+
Status: lint.Error,
151+
Details: err.Error(),
144152
}
145153
}
146154

147-
return &lint.LintResult{Status: lint.Pass}
155+
return res
148156
}
+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package rfc
2+
3+
import (
4+
"github.com/zmap/zcrypto/x509"
5+
"github.com/zmap/zlint/v3/lint"
6+
"github.com/zmap/zlint/v3/util"
7+
)
8+
9+
type crlViaPKIMetal struct {
10+
PKIMetalConfig
11+
}
12+
13+
func init() {
14+
lint.RegisterRevocationListLint(&lint.RevocationListLint{
15+
LintMetadata: lint.LintMetadata{
16+
Name: "e_pkimetal_lint_cabf_serverauth_crl",
17+
Description: "Runs pkimetal's suite of cabf serverauth CRL lints",
18+
Citation: "https://github.com/pkimetal/pkimetal",
19+
Source: lint.Community,
20+
EffectiveDate: util.CABEffectiveDate,
21+
},
22+
Lint: NewCrlViaPKIMetal,
23+
})
24+
}
25+
26+
func NewCrlViaPKIMetal() lint.RevocationListLintInterface {
27+
return &crlViaPKIMetal{}
28+
}
29+
30+
func (l *crlViaPKIMetal) Configure() any {
31+
return l
32+
}
33+
34+
func (l *crlViaPKIMetal) CheckApplies(c *x509.RevocationList) bool {
35+
// This lint applies to all CRLs issued by Boulder, as long as it has
36+
// been configured with an address to reach out to. If not, skip it.
37+
return l.Addr != ""
38+
}
39+
40+
func (l *crlViaPKIMetal) Execute(c *x509.RevocationList) *lint.LintResult {
41+
res, err := l.execute("lintcrl", c.Raw)
42+
if err != nil {
43+
return &lint.LintResult{
44+
Status: lint.Error,
45+
Details: err.Error(),
46+
}
47+
}
48+
49+
return res
50+
}

test/config-next/ca.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,8 @@
118118
},
119119
"crlProfile": {
120120
"validityInterval": "216h",
121-
"maxBackdate": "1h5m"
121+
"maxBackdate": "1h5m",
122+
"lintConfig": "test/config-next/zlint.toml"
122123
},
123124
"issuers": [
124125
{

test/config-next/zlint.toml

+6
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ ignore_lints = [
1616
# and "shortlived" profiles.
1717
"pkilint:cabf.serverauth.subscriber_rsa_digitalsignature_and_keyencipherment_present",
1818
]
19+
20+
[e_pkimetal_lint_cabf_serverauth_crl]
21+
addr = "http://10.77.77.9:8080"
22+
severity = "notice"
23+
timeout = 2000000000 # 2 seconds
24+
ignore_lints = []

test/config/ca.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@
9595
},
9696
"crlProfile": {
9797
"validityInterval": "216h",
98-
"maxBackdate": "1h5m"
98+
"maxBackdate": "1h5m",
99+
"lintConfig": "test/config/zlint.toml"
99100
},
100101
"issuers": [
101102
{

test/config/zlint.toml

+6
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ ignore_lints = [
1616
# and "shortlived" profiles.
1717
"pkilint:cabf.serverauth.subscriber_rsa_digitalsignature_and_keyencipherment_present",
1818
]
19+
20+
[e_pkimetal_lint_cabf_serverauth_crl]
21+
addr = "http://10.77.77.9:8080"
22+
severity = "notice"
23+
timeout = 2000000000 # 2 seconds
24+
ignore_lints = []

0 commit comments

Comments
 (0)