@@ -17,115 +17,80 @@ import (
17
17
"github.com/zmap/zlint/v3/util"
18
18
)
19
19
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 {
21
23
Addr string `toml:"addr" comment:"The address where a pkilint REST API can be reached."`
22
24
Severity string `toml:"severity" comment:"The minimum severity of findings to report (meta, debug, info, notice, warning, error, bug, or fatal)."`
23
25
Timeout time.Duration `toml:"timeout" comment:"How long, in nanoseconds, to wait before giving up."`
24
26
IgnoreLints []string `toml:"ignore_lints" comment:"The unique Validator:Code IDs of lint findings which should be ignored."`
25
27
}
26
28
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
66
31
if timeout == 0 {
67
32
timeout = 100 * time .Millisecond
68
33
}
69
34
70
35
ctx , cancel := context .WithTimeout (context .Background (), timeout )
71
36
defer cancel ()
72
37
38
+ apiURL , err := url .JoinPath (pkim .Addr , endpoint )
39
+ if err != nil {
40
+ return nil , fmt .Errorf ("constructing pkimetal url: %w" , err )
41
+ }
42
+
73
43
// 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.
76
46
// https://github.com/pkimetal/pkimetal/blob/578ac224a7ca3775af51b47fce16c95753d9ac8d/doc/openapi.yaml#L179-L194
77
47
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" )
80
51
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 ()))
83
53
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 )
88
55
}
89
56
req .Header .Add ("Content-Type" , "application/x-www-form-urlencoded" )
90
57
req .Header .Add ("Accept" , "application/json" )
91
58
92
59
resp , err := http .DefaultClient .Do (req )
93
60
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 )
98
62
}
99
63
defer resp .Body .Close ()
100
64
101
65
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 )
106
67
}
107
68
108
69
resJSON , err := io .ReadAll (resp .Body )
109
70
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"`
114
82
}
115
83
116
84
var res []finding
117
85
err = json .Unmarshal (resJSON , & res )
118
86
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 )
123
88
}
124
89
125
90
var findings []string
126
91
for _ , finding := range res {
127
92
id := fmt .Sprintf ("%s:%s" , finding .Linter , finding .Code )
128
- if slices .Contains (l .IgnoreLints , id ) {
93
+ if slices .Contains (pkim .IgnoreLints , id ) {
129
94
continue
130
95
}
131
96
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 {
141
106
return & lint.LintResult {
142
107
Status : lint .Error ,
143
108
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 (),
144
152
}
145
153
}
146
154
147
- return & lint. LintResult { Status : lint . Pass }
155
+ return res
148
156
}
0 commit comments