@@ -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}
0 commit comments