diff --git a/v3/go.mod b/v3/go.mod index 2a571f87a..0f32059a7 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -5,14 +5,14 @@ go 1.18 require ( github.com/kr/text v0.2.0 // indirect github.com/pelletier/go-toml v1.9.3 - github.com/sirupsen/logrus v1.8.1 - github.com/zmap/zcrypto v0.0.0-20220402174210-599ec18ecbac - golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 - golang.org/x/net v0.7.0 - golang.org/x/text v0.7.0 + github.com/sirupsen/logrus v1.9.0 + github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 + golang.org/x/crypto v0.7.0 + golang.org/x/net v0.8.0 + golang.org/x/text v0.8.0 ) require ( - github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236 // indirect - golang.org/x/sys v0.5.0 // indirect + github.com/weppos/publicsuffix-go v0.30.0 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/v3/go.sum b/v3/go.sum index 8acf11a8a..6adff4187 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -2,7 +2,10 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -10,52 +13,96 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236 h1:vMJBP3PQViZsF6cOINtvyMC8ptpLsyJ4EwyFnzuWNxc= -github.com/weppos/publicsuffix-go v0.15.1-0.20220329081811-9a40b608a236/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/weppos/publicsuffix-go v0.12.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.30.0 h1:QHPZ2GRu/YE7cvejH9iyavPOkVCB4dNxp2ZvtT+vQLY= +github.com/weppos/publicsuffix-go v0.30.0/go.mod h1:kBi8zwYnR0zrbm8RcuN1o9Fzgpnnn+btVN8uWPMyXAY= +github.com/weppos/publicsuffix-go/publicsuffix/generator v0.0.0-20220927085643-dc0d00c92642/go.mod h1:GHfoeIdZLdZmLjMlzBftbTDntahTttUMWjxZwQJhULE= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= -github.com/zmap/zcrypto v0.0.0-20220402174210-599ec18ecbac h1:+nr36qrZEH0RIYNjcUEnOrCUdcSG3om2ANaFA6iSVWA= -github.com/zmap/zcrypto v0.0.0-20220402174210-599ec18ecbac/go.mod h1:egdRkzUylATvPkWMpebZbXhv0FMEMJGX/ur0D3Csk2s= +github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= +github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300 h1:DZH5n7L3L8RxKdSyJHZt7WePgwdhHnPhQFdQSJaHF+o= +github.com/zmap/zcrypto v0.0.0-20230310154051-c8b263fd8300/go.mod h1:mOd4yUMgn2fe2nV9KXsa9AyQBFZGzygVPovsZR+Rl5w= +github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= -golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v3/lint/base.go b/v3/lint/base.go index e85526614..72c38c907 100644 --- a/v3/lint/base.go +++ b/v3/lint/base.go @@ -15,23 +15,40 @@ package lint */ import ( - "fmt" "time" "github.com/zmap/zcrypto/x509" "github.com/zmap/zlint/v3/util" ) -// LintInterface is implemented by each Lint. -type LintInterface interface { //nolint:revive +// LintInterface is implemented by each certificate linter. +// +// @deprecated - use CertificateLintInterface instead. +type LintInterface = CertificateLintInterface + +// RevocationListLintInterface is implemented by each revocation list linter. +type RevocationListLintInterface interface { + // CheckApplies runs once per revocation list. It returns true if the + // Lint should run on the given certificate. If CheckApplies returns + // false, the Lint result is automatically set to NA without calling + // CheckEffective() or Run(). + CheckApplies(r *x509.RevocationList) bool + + // Execute is the body of the lint. It is called for every revocation list + // for which CheckApplies returns true. + Execute(r *x509.RevocationList) *LintResult +} + +// CertificateLintInterface is implemented by each certificate linter. +type CertificateLintInterface interface { // CheckApplies runs once per certificate. It returns true if the Lint should // run on the given certificate. If CheckApplies returns false, the Lint // result is automatically set to NA without calling CheckEffective() or // Run(). CheckApplies(c *x509.Certificate) bool - // Execute() is the body of the lint. It is called for every certificate for - // which CheckApplies() returns true. + // Execute is the body of the lint. It is called for every certificate for + // which CheckApplies returns true. Execute(c *x509.Certificate) *LintResult } @@ -40,10 +57,45 @@ type Configurable interface { Configure() interface{} } +// LintMetadata represents the metadata that are broadly associated across all types of lints. +// +// That is, all lints (irrespective of being a certificate lint, a CRL lint, and OCSP, etc.) +// have a Name, a Description, a Citation, and so on. +// +// In this way, this struct may be embedded in any linting type in order to maintain this +// data, while each individual linting type provides the behavior over this data. +type LintMetadata struct { + // Name is a lowercase underscore-separated string describing what a given + // Lint checks. If Name beings with "w", the lint MUST NOT return Error, only + // Warn. If Name beings with "e", the Lint MUST NOT return Warn, only Error. + Name string `json:"name,omitempty"` + + // A human-readable description of what the Lint checks. Usually copied + // directly from the CA/B Baseline Requirements or RFC 5280. + Description string `json:"description,omitempty"` + + // The source of the check, e.g. "BRs: 6.1.6" or "RFC 5280: 4.1.2.6". + Citation string `json:"citation,omitempty"` + + // Programmatic source of the check, BRs, RFC5280, or ZLint + Source LintSource `json:"source"` + + // Lints automatically returns NE for all certificates where CheckApplies() is + // true but with NotBefore < EffectiveDate. This check is bypassed if + // EffectiveDate is zero. Please see CheckEffective for more information. + EffectiveDate time.Time `json:"-"` + + // Lints automatically returns NE for all certificates where CheckApplies() is + // true but with NotBefore >= IneffectiveDate. This check is bypassed if + // IneffectiveDate is zero. Please see CheckEffective for more information. + IneffectiveDate time.Time `json:"-"` +} + // A Lint struct represents a single lint, e.g. // "e_basic_constraints_not_critical". It contains an implementation of LintInterface. +// +// @deprecated - use CertificateLint instead. type Lint struct { - // Name is a lowercase underscore-separated string describing what a given // Lint checks. If Name beings with "w", the lint MUST NOT return Error, only // Warn. If Name beings with "e", the Lint MUST NOT return Warn, only Error. @@ -68,11 +120,27 @@ type Lint struct { // true but with NotBefore >= IneffectiveDate. This check is bypassed if // IneffectiveDate is zero. Please see CheckEffective for more information. IneffectiveDate time.Time `json:"-"` - // A constructor which returns the implementation of the lint logic. Lint func() LintInterface `json:"-"` } +// toCertificateLint converts a Lint to a CertificateLint for backwards compatibility. +// +// @deprecated - Use CertificateLint directly. +func (l *Lint) toCertificateLint() *CertificateLint { + return &CertificateLint{ + LintMetadata: LintMetadata{ + Name: l.Name, + Description: l.Description, + Citation: l.Citation, + Source: l.Source, + EffectiveDate: l.EffectiveDate, + IneffectiveDate: l.IneffectiveDate, + }, + Lint: l.Lint, + } +} + // CheckEffective returns true if c was issued on or after the EffectiveDate // AND before (but not on) the Ineffective date. That is, CheckEffective // returns true if... @@ -82,10 +150,10 @@ type Lint struct { // If EffectiveDate is zero, then only IneffectiveDate is checked. Conversely, // if IneffectiveDate is zero then only EffectiveDate is checked. If both EffectiveDate // and IneffectiveDate are zero then CheckEffective always returns true. +// +// @deprecated - use CertificateLint instead. func (l *Lint) CheckEffective(c *x509.Certificate) bool { - onOrAfterEffective := l.EffectiveDate.IsZero() || util.OnOrAfter(c.NotBefore, l.EffectiveDate) - strictlyBeforeIneffective := l.IneffectiveDate.IsZero() || c.NotBefore.Before(l.IneffectiveDate) - return onOrAfterEffective && strictlyBeforeIneffective + return l.toCertificateLint().CheckEffective(c) } // Execute runs the lint against a certificate. For lints that are @@ -97,34 +165,132 @@ func (l *Lint) CheckEffective(c *x509.Certificate) bool { // CheckApplies() // CheckEffective() // Execute() +// +// @deprecated - use CertificateLint instead func (l *Lint) Execute(cert *x509.Certificate, config Configuration) *LintResult { + return l.toCertificateLint().Execute(cert, config) +} + +// CertificateLint represents a single x509 certificate linter. +type CertificateLint struct { + // Metadata associated with the linter. + LintMetadata + // A constructor which returns the implementation of the linter. + Lint func() CertificateLintInterface `json:"-"` +} + +// toLint converts a CertificateLint to Lint for backwards compatibility +// +// @deprecated - use CertificateLint directly. +func (l *CertificateLint) toLint() *Lint { + return &Lint{ + Name: l.Name, + Description: l.Description, + Citation: l.Citation, + Source: l.Source, + EffectiveDate: l.EffectiveDate, + IneffectiveDate: l.IneffectiveDate, + Lint: l.Lint, + } +} + +// CheckEffective returns true if c was issued on or after the EffectiveDate +// AND before (but not on) the Ineffective date. That is, CheckEffective +// returns true if... +// +// c.NotBefore in [EffectiveDate, IneffectiveDate) +// +// If EffectiveDate is zero, then only IneffectiveDate is checked. Conversely, +// if IneffectiveDate is zero then only EffectiveDate is checked. If both EffectiveDate +// and IneffectiveDate are zero then CheckEffective always returns true. +func (l *CertificateLint) CheckEffective(c *x509.Certificate) bool { + return checkEffective(l.EffectiveDate, l.IneffectiveDate, c.NotBefore) +} + +// Execute runs the lint against a certificate. For lints that are +// sourced from the CA/B Forum Baseline Requirements, we first determine +// if they are within the purview of the BRs. See CertificateLintInterface +// for details about the other methods called. +// The ordering is as follows: +// +// Configure() ----> only if the lint implements Configurable +// CheckApplies() +// CheckEffective() +// Execute() +func (l *CertificateLint) Execute(cert *x509.Certificate, config Configuration) *LintResult { if l.Source == CABFBaselineRequirements && !util.IsServerAuthCert(cert) { return &LintResult{Status: NA} } - return l.execute(l.Lint(), cert, config) -} - -func (l *Lint) execute(lint LintInterface, cert *x509.Certificate, config Configuration) *LintResult { - configurable, ok := lint.(Configurable) - if ok { - err := config.Configure(configurable.Configure(), l.Name) - if err != nil { - details := fmt.Sprintf( - "A fatal error occurred while attempting to configure %s. Please visit the [%s] section of "+ - "your provided configuration and compare it with the output of `zlint -exampleConfig`. Error: %s", - l.Name, - l.Name, - err.Error()) - return &LintResult{ - Status: Fatal, - Details: details} - } + lint := l.Lint() + err := config.MaybeConfigure(lint, l.Name) + if err != nil { + return &LintResult{ + Status: Fatal, + Details: err.Error()} } if !lint.CheckApplies(cert) { return &LintResult{Status: NA} } else if !l.CheckEffective(cert) { return &LintResult{Status: NE} } - res := lint.Execute(cert) - return res + return lint.Execute(cert) +} + +// RevocationListLint represents a single x509 CRL linter. +type RevocationListLint struct { + // Metadata associated with the linter. + LintMetadata + // A constructor which returns the implementation of the linter. + Lint func() RevocationListLintInterface `json:"-"` +} + +// CheckEffective returns true if r was generated on or after the EffectiveDate +// AND before (but not on) the Ineffective date. That is, CheckEffective +// returns true if... +// +// r.ThisUpdate in [EffectiveDate, IneffectiveDate) +// +// If EffectiveDate is zero, then only IneffectiveDate is checked. Conversely, +// if IneffectiveDate is zero then only EffectiveDate is checked. If both EffectiveDate +// and IneffectiveDate are zero then CheckEffective always returns true. +func (l *RevocationListLint) CheckEffective(r *x509.RevocationList) bool { + return checkEffective(l.EffectiveDate, l.IneffectiveDate, r.ThisUpdate) +} + +// Execute runs the lint against a revocation list. +// The ordering is as follows: +// +// Configure() ----> only if the lint implements Configurable +// CheckApplies() +// CheckEffective() +// Execute() +func (l *RevocationListLint) Execute(r *x509.RevocationList, config Configuration) *LintResult { + lint := l.Lint() + err := config.MaybeConfigure(lint, l.Name) + if err != nil { + return &LintResult{ + Status: Fatal, + Details: err.Error()} + } + if !lint.CheckApplies(r) { + return &LintResult{Status: NA} + } else if !l.CheckEffective(r) { + return &LintResult{Status: NE} + } + return lint.Execute(r) +} + +// checkEffective returns true if target was generated on or after the EffectiveDate +// AND before (but not on) the Ineffective date. That is, CheckEffective +// returns true if... +// +// target in [effective, ineffective) +// +// If effective is zero, then only ineffective is checked. Conversely, +// if ineffective is zero then only effect is checked. If both effective +// and ineffective are zero then checkEffective always returns true. +func checkEffective(effective, ineffective, target time.Time) bool { + onOrAfterEffective := effective.IsZero() || util.OnOrAfter(target, effective) + strictlyBeforeIneffective := ineffective.IsZero() || target.Before(ineffective) + return onOrAfterEffective && strictlyBeforeIneffective } diff --git a/v3/lint/base_test.go b/v3/lint/base_test.go index 10ba29d45..882f07f47 100644 --- a/v3/lint/base_test.go +++ b/v3/lint/base_test.go @@ -166,3 +166,149 @@ func TestLint_CheckEffective(t *testing.T) { } } } + +// This test attempts to simplify the truth table by assigning dates to the +// single digit values 1 through 5, inclusive. As per the standard library, +// 0 is taken to be the null value. +// +// E.G. +// +// If a lint is effective between 2 and 5, then the revocation lists {2, 3, 4} return true. +// If a lint is effective between 0 and 4, then the revocation lists {0, 1, 2, 3} return true. +// If a lint is effective between 2 and 0, then the revocation lists {2, 3, 4, 5} return true. +// If a lint is effective between 0 and 0, then the revocation lists {0, 1, 2, 3, 4, 5} return true. +func TestLint_RevocationListLint_CheckEffective(t *testing.T) { + zero := time.Time{} + one := time.Unix(1, 0) + two := time.Unix(2, 0) + three := time.Unix(3, 0) + four := time.Unix(4, 0) + five := time.Unix(5, 0) + lZeroZero := RevocationListLint{LintMetadata: LintMetadata{ + Description: "ZeroZero", + EffectiveDate: zero, IneffectiveDate: zero}, + } + lTwoZero := RevocationListLint{LintMetadata: LintMetadata{ + Description: "TwoZero", + EffectiveDate: two, IneffectiveDate: zero}} + lZeroFour := RevocationListLint{LintMetadata: LintMetadata{ + Description: "ZeroFour", + EffectiveDate: zero, IneffectiveDate: four}} + lTwoFour := RevocationListLint{LintMetadata: LintMetadata{ + Description: "TwoFour", + EffectiveDate: two, IneffectiveDate: four}} + + type revocationList struct { + Description string + RevocationList *x509.RevocationList + } + + cZero := revocationList{ + Description: "cZero", + RevocationList: &x509.RevocationList{ThisUpdate: zero}, + } + cOne := revocationList{ + Description: "cOne", + RevocationList: &x509.RevocationList{ThisUpdate: one}, + } + cTwo := revocationList{ + Description: "cTwo", + RevocationList: &x509.RevocationList{ThisUpdate: two}, + } + cThree := revocationList{ + Description: "cThree", + RevocationList: &x509.RevocationList{ThisUpdate: three}, + } + cFour := revocationList{ + Description: "cFour", + RevocationList: &x509.RevocationList{ThisUpdate: four}, + } + cFive := revocationList{ + Description: "cFive", + RevocationList: &x509.RevocationList{ThisUpdate: five}, + } + + data := []struct { + Lint RevocationListLint + RevocationList revocationList + Want bool + }{ + /////////////// + { + Lint: lZeroZero, + RevocationList: cZero, + Want: true, + }, + { + Lint: lZeroZero, + RevocationList: cOne, + Want: true, + }, + ////////// + { + Lint: lTwoZero, + RevocationList: cOne, + Want: false, + }, + { + Lint: lTwoZero, + RevocationList: cTwo, + Want: true, + }, + { + Lint: lTwoZero, + RevocationList: cThree, + Want: true, + }, + /////////////// + { + Lint: lZeroFour, + RevocationList: cTwo, + Want: true, + }, + { + Lint: lZeroFour, + RevocationList: cFour, + Want: false, + }, + { + Lint: lZeroFour, + RevocationList: cFive, + Want: false, + }, + //////////// + { + Lint: lTwoFour, + RevocationList: cOne, + Want: false, + }, + { + Lint: lTwoFour, + RevocationList: cTwo, + Want: true, + }, + { + Lint: lTwoFour, + RevocationList: cThree, + Want: true, + }, + { + Lint: lTwoFour, + RevocationList: cFour, + Want: false, + }, + { + Lint: lTwoFour, + RevocationList: cFive, + Want: false, + }, + } + + for _, d := range data { + got := d.Lint.CheckEffective(d.RevocationList.RevocationList) + if got != d.Want { + t.Errorf("Lint %s, revocation list %s, got %v want %v", + d.Lint.Description, d.RevocationList.Description, got, d.Want) + } + } +} diff --git a/v3/lint/configuration.go b/v3/lint/configuration.go index 19e1f7936..7b81dcc29 100644 --- a/v3/lint/configuration.go +++ b/v3/lint/configuration.go @@ -15,6 +15,7 @@ package lint import ( + "errors" "fmt" "io" "os" @@ -30,6 +31,20 @@ type Configuration struct { tree *toml.Tree } +// MaybeConfigure is a thin wrapper over Configure. +// +// If the provided lint object does not implement the Configurable interface +// then this function is a noop and nil is always returned. +// +// Otherwise, configuration of the provided lint is attempted. +func (c Configuration) MaybeConfigure(lint interface{}, namespace string) error { + configurable, ok := lint.(Configurable) + if !ok { + return nil + } + return c.Configure(configurable.Configure(), namespace) +} + // Configure attempts to deserialize the provided namespace into the provided empty interface. // // For example, let's say that the name of your lint is MyLint, then the configuration @@ -60,7 +75,17 @@ type Configuration struct { // configuration.Configure(&myLint, myLint.Name()) // ``` func (c Configuration) Configure(lint interface{}, namespace string) error { - return c.deserializeConfigInto(lint, namespace) + err := c.deserializeConfigInto(lint, namespace) + if err != nil { + details := fmt.Sprintf( + "A fatal error occurred while attempting to configure %s. Please visit the [%s] section of "+ + "your provided configuration and compare it with the output of `zlint -exampleConfig`. Error: %s", + namespace, + namespace, + err.Error()) + err = errors.New(details) + } + return err } // NewConfig attempts to instantiate a configuration by consuming the contents of the provided reader. diff --git a/v3/lint/lint_lookup.go b/v3/lint/lint_lookup.go new file mode 100644 index 000000000..6f9a0feab --- /dev/null +++ b/v3/lint/lint_lookup.go @@ -0,0 +1,217 @@ +/* + * ZLint Copyright 2021 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 the License for the specific language governing + * permissions and limitations under the License. + */ + +package lint + +import ( + "sort" + "sync" +) + +var ( + // Verify that the interface holds + _ linterLookup = &linterLookupImpl{} + _ CertificateLinterLookup = &certificateLinterLookupImpl{} + _ RevocationListLinterLookup = &revocationListLinterLookupImpl{} +) + +type linterLookup interface { + // Names returns a list of all lint names that have been registered. + // The returned list is sorted by lexicographical ordering. + Names() []string + // Sources returns a SourceList of registered LintSources. The list is not + // sorted but can be sorted by the caller with sort.Sort() if required. + Sources() SourceList +} + +type linterLookupImpl struct { + sync.RWMutex + // lintNames is a sorted list of all registered lint names. It is + // equivalent to collecting the keys from lintsByName into a slice and sorting + // them lexicographically. + lintNames []string + sources map[LintSource]struct{} +} + +// Names returns the list of lint names registered for the lint type T. +func (lookup *linterLookupImpl) Names() []string { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lintNames +} + +// Sources returns a SourceList of registered LintSources. The list is not +// sorted but can be sorted by the caller with sort.Sort() if required. +func (lookup *linterLookupImpl) Sources() SourceList { + lookup.RLock() + defer lookup.RUnlock() + var list SourceList + for lintSource := range lookup.sources { + list = append(list, lintSource) + } + return list +} + +func newLinterLookup() linterLookupImpl { + return linterLookupImpl{ + lintNames: make([]string, 0), + sources: map[LintSource]struct{}{}, + } +} + +// CertificateLinterLookup is an interface describing how registered certificate lints can be looked up. +type CertificateLinterLookup interface { + linterLookup + // ByName returns a pointer to the registered lint with the given name, or nil + // if there is no such lint registered in the registry. + ByName(name string) *CertificateLint + // BySource returns a list of registered lints that have the same LintSource as + // provided (or nil if there were no such lints in the registry). + BySource(s LintSource) []*CertificateLint + // Lints returns a list of all the lints registered. + Lints() []*CertificateLint +} + +type certificateLinterLookupImpl struct { + linterLookupImpl + // lintsByName is a map of all registered lints by name. + lintsByName map[string]*CertificateLint + lintsBySource map[LintSource][]*CertificateLint + lints []*CertificateLint +} + +// ByName returns the Lint previously registered under the given name with +// Register, or nil if no matching lint name has been registered. +func (lookup *certificateLinterLookupImpl) ByName(name string) *CertificateLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lintsByName[name] +} + +// BySource returns a list of registered lints that have the same LintSource as +// provided (or nil if there were no such lints). +func (lookup *certificateLinterLookupImpl) BySource(s LintSource) []*CertificateLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lintsBySource[s] +} + +// Lints returns a list of registered lints. +func (lookup *certificateLinterLookupImpl) Lints() []*CertificateLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lints +} + +func (lookup *certificateLinterLookupImpl) register(lint *CertificateLint, name string, source LintSource) error { + if name == "" { + return errEmptyName + } + lookup.RLock() + defer lookup.RUnlock() + + if existing := lookup.lintsByName[name]; existing != nil { + return &errDuplicateName{name} + } + lookup.lints = append(lookup.lints, lint) + lookup.lintNames = append(lookup.lintNames, name) + lookup.lintsByName[name] = lint + + lookup.sources[source] = struct{}{} + lookup.lintsBySource[source] = append(lookup.lintsBySource[source], lint) + sort.Strings(lookup.lintNames) + return nil +} + +func newCertificateLintLookup() certificateLinterLookupImpl { + return certificateLinterLookupImpl{ + linterLookupImpl: newLinterLookup(), + lintsByName: make(map[string]*CertificateLint), + lintsBySource: make(map[LintSource][]*CertificateLint), + lints: make([]*CertificateLint, 0), + } +} + +// RevocationListLinterLookup is an interface describing how registered revocation list lints can be looked up. +type RevocationListLinterLookup interface { + linterLookup + // ByName returns a pointer to the registered lint with the given name, or nil + // if there is no such lint registered in the registry. + ByName(name string) *RevocationListLint + // BySource returns a list of registered lints that have the same LintSource as + // provided (or nil if there were no such lints in the registry). + BySource(s LintSource) []*RevocationListLint + // Lints returns a list of all the lints registered. + Lints() []*RevocationListLint +} + +type revocationListLinterLookupImpl struct { + linterLookupImpl + // lintsByName is a map of all registered lints by name. + lintsByName map[string]*RevocationListLint + lintsBySource map[LintSource][]*RevocationListLint + lints []*RevocationListLint +} + +// ByName returns the Lint previously registered under the given name with +// Register, or nil if no matching lint name has been registered. +func (lookup *revocationListLinterLookupImpl) ByName(name string) *RevocationListLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lintsByName[name] +} + +// BySource returns a list of registered lints that have the same LintSource as +// provided (or nil if there were no such lints). +func (lookup *revocationListLinterLookupImpl) BySource(s LintSource) []*RevocationListLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lintsBySource[s] +} + +// Lints returns a list of registered lints. +func (lookup *revocationListLinterLookupImpl) Lints() []*RevocationListLint { + lookup.RLock() + defer lookup.RUnlock() + return lookup.lints +} + +func (lookup *revocationListLinterLookupImpl) register(lint *RevocationListLint, name string, source LintSource) error { + if name == "" { + return errEmptyName + } + lookup.RLock() + defer lookup.RUnlock() + + if existing := lookup.lintsByName[name]; existing != nil { + return &errDuplicateName{name} + } + lookup.lints = append(lookup.lints, lint) + lookup.lintNames = append(lookup.lintNames, name) + lookup.lintsByName[name] = lint + + lookup.sources[source] = struct{}{} + lookup.lintsBySource[source] = append(lookup.lintsBySource[source], lint) + sort.Strings(lookup.lintNames) + return nil +} + +func newRevocationListLintLookup() revocationListLinterLookupImpl { + return revocationListLinterLookupImpl{ + linterLookupImpl: newLinterLookup(), + lintsByName: make(map[string]*RevocationListLint), + lintsBySource: make(map[LintSource][]*RevocationListLint), + lints: make([]*RevocationListLint, 0), + } +} diff --git a/v3/lint/registration.go b/v3/lint/registration.go index 7415999c1..52fe028e0 100644 --- a/v3/lint/registration.go +++ b/v3/lint/registration.go @@ -23,7 +23,6 @@ import ( "regexp" "sort" "strings" - "sync" "github.com/pelletier/go-toml" ) @@ -80,7 +79,7 @@ func (f *FilterOptions) AddProfile(profile Profile) { // Typically users will interact with the global Registry returned by // GlobalRegistry(), or a filtered Registry created by applying FilterOptions to // the GlobalRegistry()'s Filter function. -type Registry interface { +type Registry interface { //nolint: interfacebloat // Somewhat unavoidable here. // Names returns a list of all of the lint names that have been registered // in string sorted order. Names() []string @@ -91,9 +90,13 @@ type Registry interface { DefaultConfiguration() ([]byte, error) // ByName returns a pointer to the registered lint with the given name, or nil // if there is no such lint registered in the registry. + // + // @deprecated - use CertificateLints instead. ByName(name string) *Lint // BySource returns a list of registered lints that have the same LintSource as // provided (or nil if there were no such lints in the registry). + // + // @deprecated - use CertificateLints instead. BySource(s LintSource) []*Lint // Filter returns a new Registry containing only lints that match the // FilterOptions criteria. @@ -103,22 +106,18 @@ type Registry interface { WriteJSON(w io.Writer) SetConfiguration(config Configuration) GetConfiguration() Configuration + // CertificateLints returns an interface used to lookup CertificateLints. + CertificateLints() CertificateLinterLookup + // RevocationListLitns returns an interface used to lookup RevocationListLints. + RevocationListLints() RevocationListLinterLookup } // registryImpl implements the Registry interface to provide a global collection // of Lints that have been registered. type registryImpl struct { - sync.RWMutex - // lintsByName is a map of all registered lints by name. - lintsByName map[string]*Lint - // lintNames is a sorted list of all of the registered lint names. It is - // equivalent to collecting the keys from lintsByName into a slice and sorting - // them lexicographically. - lintNames []string - // lintsBySource is a map of all registered lints by source category. Lints - // are added to the lintsBySource map by RegisterLint. - lintsBySource map[LintSource][]*Lint - configuration Configuration + certificateLints certificateLinterLookupImpl + revocationListLints revocationListLinterLookupImpl + configuration Configuration } var ( @@ -144,67 +143,106 @@ func (e errDuplicateName) Error() string { e.lintName) } -// register adds the provided lint to the Registry. If initialize is true then -// the lint's Initialize() function will be called before registering the lint. +// registerLint registers a lint to the registry. +// +// @deprecated - use registerCertificateLint instead. +func (r *registryImpl) register(l *Lint) error { + if l == nil { + return errNilLint + } + if l.Lint() == nil { + return errNilLintPtr + } + + return r.registerCertificateLint(l.toCertificateLint()) +} + +// registerCertificateLint registers a CertificateLint to the registry. // // An error is returned if the lint or lint's Lint pointer is nil, if the Lint // has an empty Name or if the Name was previously registered. -func (r *registryImpl) register(l *Lint) error { +func (r *registryImpl) registerCertificateLint(l *CertificateLint) error { if l == nil { return errNilLint } if l.Lint() == nil { return errNilLintPtr } - if l.Name == "" { - return errEmptyName + return r.certificateLints.register(l, l.Name, l.Source) +} + +// registerCertificateLint registers a CertificateLint to the registry. +// +// An error is returned if the lint or lint's Lint pointer is nil, if the Lint +// has an empty Name or if the Name was previously registered. +func (r *registryImpl) registerRevocationListLint(l *RevocationListLint) error { + if l == nil { + return errNilLint } - if existing := r.ByName(l.Name); existing != nil { - return &errDuplicateName{l.Name} + if l.Lint() == nil { + return errNilLintPtr } - r.Lock() - defer r.Unlock() - r.lintNames = append(r.lintNames, l.Name) - r.lintsByName[l.Name] = l - r.lintsBySource[l.Source] = append(r.lintsBySource[l.Source], l) - sort.Strings(r.lintNames) - return nil + return r.revocationListLints.register(l, l.Name, l.Source) } // ByName returns the Lint previously registered under the given name with // Register, or nil if no matching lint name has been registered. +// +// @deprecated - use r.CertificateLints.ByName() instead. func (r *registryImpl) ByName(name string) *Lint { - r.RLock() - defer r.RUnlock() - return r.lintsByName[name] + certificateLint := r.certificateLints.ByName(name) + if certificateLint == nil { + return nil + } + + return certificateLint.toLint() } // Names returns a list of all of the lint names that have been registered // in string sorted order. func (r *registryImpl) Names() []string { - r.RLock() - defer r.RUnlock() - return r.lintNames + var names []string + names = append(names, r.certificateLints.lintNames...) + names = append(names, r.revocationListLints.lintNames...) + + sort.Strings(names) + return names } // BySource returns a list of registered lints that have the same LintSource as // provided (or nil if there were no such lints). +// +// @deprecated use r.CertificateLints().BySource() instead. func (r *registryImpl) BySource(s LintSource) []*Lint { - r.RLock() - defer r.RUnlock() - return r.lintsBySource[s] + var lints []*Lint + + certificateLints := r.certificateLints.BySource(s) + for _, l := range certificateLints { + if l == nil { + continue + } + lints = append(lints, l.toLint()) + } + + return lints } // Sources returns a SourceList of registered LintSources. The list is not // sorted but can be sorted by the caller with sort.Sort() if required. func (r *registryImpl) Sources() SourceList { - r.RLock() - defer r.RUnlock() - var results SourceList - for k := range r.lintsBySource { - results = append(results, k) - } - return results + var sources SourceList + + sources = append(sources, r.certificateLints.Sources()...) + sources = append(sources, r.revocationListLints.Sources()...) + return sources +} + +func (r *registryImpl) CertificateLints() CertificateLinterLookup { + return &r.certificateLints +} + +func (r *registryImpl) RevocationListLints() RevocationListLinterLookup { + return &r.revocationListLints } // lintNamesToMap converts a list of lit names into a bool hashmap useful for @@ -218,10 +256,15 @@ func (r *registryImpl) lintNamesToMap(names []string) (map[string]bool, error) { namesMap := make(map[string]bool, len(names)) for _, n := range names { n = strings.TrimSpace(n) - if l := r.ByName(n); l == nil { - return nil, fmt.Errorf("unknown lint name %q", n) + if l := r.certificateLints.ByName(n); l != nil { + namesMap[n] = true + continue } - namesMap[n] = true + if l := r.revocationListLints.ByName(n); l != nil { + namesMap[n] = true + continue + } + return nil, fmt.Errorf("unknown lint name %q", n) } return namesMap, nil } @@ -273,12 +316,25 @@ func (r *registryImpl) Filter(opts FilterOptions) (Registry, error) { } for _, name := range r.Names() { - l := r.ByName(name) + var meta LintMetadata + var registerFunc func() error - if sourceExcludes != nil && sourceExcludes[l.Source] { + if l := r.certificateLints.ByName(name); l != nil { + meta = l.LintMetadata + registerFunc = func() error { + return filteredRegistry.registerCertificateLint(l) + } + } else if l := r.revocationListLints.ByName(name); l != nil { + meta = l.LintMetadata + registerFunc = func() error { + return filteredRegistry.registerRevocationListLint(l) + } + } + + if sourceExcludes != nil && sourceExcludes[meta.Source] { continue } - if sourceIncludes != nil && !sourceIncludes[l.Source] { + if sourceIncludes != nil && !sourceIncludes[meta.Source] { continue } if opts.NameFilter != nil && !opts.NameFilter.MatchString(name) { @@ -291,9 +347,7 @@ func (r *registryImpl) Filter(opts FilterOptions) (Registry, error) { continue } - // when adding lints to a filtered registry we do not want Initialize() to - // be called a second time, so provide false as the initialize argument. - if err := filteredRegistry.register(l); err != nil { + if err := registerFunc(); err != nil { return nil, err } } @@ -306,9 +360,14 @@ func (r *registryImpl) Filter(opts FilterOptions) (Registry, error) { func (r *registryImpl) WriteJSON(w io.Writer) { enc := json.NewEncoder(w) enc.SetEscapeHTML(false) - for _, name := range r.Names() { + for _, lint := range r.certificateLints.Lints() { //nolint:errchkjson - _ = enc.Encode(r.ByName(name)) + _ = enc.Encode(lint) + } + + for _, lint := range r.revocationListLints.Lints() { + //nolint:errchkjson + _ = enc.Encode(lint) } } @@ -333,13 +392,22 @@ func (r *registryImpl) DefaultConfiguration() ([]byte, error) { // for the sake of making unit testing easier. func (r *registryImpl) defaultConfiguration(globals []GlobalConfiguration) ([]byte, error) { configurables := map[string]interface{}{} - for name, lint := range r.lintsByName { + for name, lint := range r.certificateLints.lintsByName { + switch configurable := lint.Lint().(type) { + case Configurable: + configurables[name] = stripGlobalsFromExample(configurable.Configure()) + default: + } + } + + for name, lint := range r.revocationListLints.lintsByName { switch configurable := lint.Lint().(type) { case Configurable: configurables[name] = stripGlobalsFromExample(configurable.Configure()) default: } } + for _, config := range globals { switch config.(type) { case *Global: @@ -375,8 +443,8 @@ func (r *registryImpl) defaultConfiguration(globals []GlobalConfiguration) ([]by //nolint:revive func NewRegistry() *registryImpl { registry := ®istryImpl{ - lintsByName: make(map[string]*Lint), - lintsBySource: make(map[LintSource][]*Lint), + certificateLints: newCertificateLintLookup(), + revocationListLints: newRevocationListLintLookup(), } registry.SetConfiguration(NewEmptyConfig()) return registry @@ -389,18 +457,41 @@ var globalRegistry = NewRegistry() // RegisterLint must be called once for each lint to be executed. Normally, // RegisterLint is called from the Go init() function of a lint implementation. // -// RegsterLint will call l.Lint's Initialize() function as part of the -// registration process. -// // IMPORTANT: RegisterLint will panic if given a nil lint, or a lint with a nil // Lint pointer, or if the lint's Initialize function errors, or if the lint // name matches a previously registered lint's name. These conditions all // indicate a bug that should be addressed by a developer. +// +// @deprecated - use RegisterCertificateLint instead. func RegisterLint(l *Lint) { + RegisterCertificateLint(l.toCertificateLint()) +} + +// RegisterCertificateLint must be called once for each CertificateLint to be executed. +// Normally, RegisterCertificateLint is called from the Go init() function of a lint implementation. +// +// IMPORTANT: RegisterCertificateLint will panic if given a nil lint, or a lint with a nil +// Lint pointer, or if the lint's Initialize function errors, or if the lint +// name matches a previously registered lint's name. These conditions all +// indicate a bug that should be addressed by a developer. +func RegisterCertificateLint(l *CertificateLint) { + if err := globalRegistry.registerCertificateLint(l); err != nil { + panic(fmt.Sprintf("RegisterLint error: %v\n", err.Error())) + } +} + +// RegisterRevocationListLint must be called once for each RevocationListLint to be executed. +// Normally, RegisterRevocationListLint is called from the Go init() function of a lint implementation. +// +// IMPORTANT: RegisterRevocationListLint will panic if given a nil lint, or a lint with a nil +// Lint pointer, or if the lint's Initialize function errors, or if the lint +// name matches a previously registered lint's name. These conditions all +// indicate a bug that should be addressed by a developer. +func RegisterRevocationListLint(l *RevocationListLint) { // RegisterLint always sets initialize to true. It's assumed this is called by // the package init() functions and therefore must be doing the first // initialization of a lint. - if err := globalRegistry.register(l); err != nil { + if err := globalRegistry.registerRevocationListLint(l); err != nil { panic(fmt.Sprintf("RegisterLint error: %v\n", err.Error())) } } diff --git a/v3/lint/registration_test.go b/v3/lint/registration_test.go index 615afb319..f215b8ae5 100644 --- a/v3/lint/registration_test.go +++ b/v3/lint/registration_test.go @@ -23,28 +23,27 @@ import ( "github.com/zmap/zcrypto/x509" ) -func TestAllLintsHaveNameDescriptionSource(t *testing.T) { - for _, name := range GlobalRegistry().Names() { - lint := GlobalRegistry().ByName(name) - if lint.Name == "" { - t.Errorf("lint %s has empty name", name) +func TestAllLintsHaveValidMeta(t *testing.T) { + checkMeta := func(meta LintMetadata) { + if meta.Name == "" { + t.Errorf("lint %s has empty name", meta.Name) } - if lint.Description == "" { - t.Errorf("lint %s has empty description", name) + if meta.Description == "" { + t.Errorf("lint %s has empty description", meta.Name) } - if lint.Citation == "" { - t.Errorf("lint %s has empty citation", name) + if meta.Citation == "" { + t.Errorf("lint %s has empty citation", meta.Name) } - } -} - -func TestAllLintsHaveSource(t *testing.T) { - for _, name := range globalRegistry.Names() { - lint := GlobalRegistry().ByName(name) - if lint.Source == UnknownLintSource { - t.Errorf("lint %s has unknown source", name) + if meta.Source == UnknownLintSource { + t.Errorf("lint %s has unknown source", meta.Name) } } + for _, lint := range globalRegistry.certificateLints.lints { + checkMeta(lint.LintMetadata) + } + for _, lint := range globalRegistry.revocationListLints.lints { + checkMeta(lint.LintMetadata) + } } func TestFilterOptionsEmpty(t *testing.T) { @@ -68,6 +67,16 @@ func (m mockLint) Execute(c *x509.Certificate) *LintResult { return nil } +type mockRevocationListLint struct{} + +func (m mockRevocationListLint) CheckApplies(c *x509.RevocationList) bool { + return true +} + +func (m mockRevocationListLint) Execute(c *x509.RevocationList) *LintResult { + return nil +} + func TestRegister(t *testing.T) { egLint := &Lint{ Name: "mockLint", @@ -152,6 +161,117 @@ func TestRegister(t *testing.T) { } } +func TestRegistryLookupEngine(t *testing.T) { + expectedNames := []string{ + "A-mockCertificateLint", + "B-mockLint", + "C-mockRevocationListLint", + } + + expectedSources := []LintSource{ + Community, + RFC3279, + RFC8813, + } + + egCertificateLint := &CertificateLint{ + LintMetadata: LintMetadata{ + Name: "A-mockCertificateLint", + Source: Community, + }, + Lint: func() CertificateLintInterface { return &mockLint{} }, + } + + egLint := &Lint{ + Name: "B-mockLint", + Lint: func() LintInterface { return &mockLint{} }, + Source: RFC8813, // arbitrary value for testing + } + + egRevocationListLint := &RevocationListLint{ + LintMetadata: LintMetadata{ + Name: "C-mockRevocationListLint", + Source: RFC3279, // arbitrary value for testing + }, + Lint: func() RevocationListLintInterface { return &mockRevocationListLint{} }, + } + + registry := NewRegistry() + if err := registry.register(egLint); err != nil { + t.Fatalf("registry.register failed: %v", err) + } + if err := registry.registerCertificateLint(egCertificateLint); err != nil { + t.Fatalf("registry.registerCertificateLint failed: %v", err) + } + if err := registry.registerRevocationListLint(egRevocationListLint); err != nil { + t.Fatalf("registry.registerRevocationListLint failed: %v", err) + } + t.Run("lint names are correct and sorted", func(t *testing.T) { + if !reflect.DeepEqual(registry.Names(), expectedNames) { + t.Fatalf("expected lint names: %v, got: %v", registry.Names(), expectedNames) + } + }) + + t.Run("sources are valid", func(t *testing.T) { + sources := registry.Sources() + sort.Sort(sources) + for i, source := range sources { + if source != expectedSources[i] { + t.Fatalf("expected source names: %v, got: %v", sources, expectedSources) + } + } + }) + + t.Run("stores contain correct lints", func(t *testing.T) { + testCases := []struct { + name string + deprecatedStore bool + certificateStore bool + revocationListStore bool + }{ + { + name: "A-mockCertificateLint", + deprecatedStore: true, + certificateStore: true, + revocationListStore: false, + }, + { + name: "B-mockLint", + deprecatedStore: true, + certificateStore: true, + revocationListStore: false, + }, + { + name: "C-mockRevocationListLint", + deprecatedStore: false, + certificateStore: false, + revocationListStore: true, + }, + } + + for _, tc := range testCases { + { + lint := registry.ByName(tc.name) + if (lint != nil) != tc.deprecatedStore { + t.Fatalf("expected lint %s to be %t (true = present, false = absent) in deprecated store", tc.name, tc.deprecatedStore) + } + } + { + lint := registry.CertificateLints().ByName(tc.name) + if (lint != nil) != tc.certificateStore { + t.Fatalf("expected lint %s to be %t (true = present, false = absent) in certificate store", tc.name, tc.certificateStore) + } + } + { + lint := registry.RevocationListLints().ByName(tc.name) + if (lint != nil) != tc.revocationListStore { + t.Fatalf("expected lint %s to be %t (true = present, false = absent) in revocationList store", tc.name, tc.revocationListStore) + } + } + } + }) +} + func TestRegistryFilter(t *testing.T) { testLint := func(name string, source LintSource) *Lint { return &Lint{ diff --git a/v3/lints/rfc/lint_crl_has_next_update.go b/v3/lints/rfc/lint_crl_has_next_update.go new file mode 100644 index 000000000..4dd434d8c --- /dev/null +++ b/v3/lints/rfc/lint_crl_has_next_update.go @@ -0,0 +1,56 @@ +package rfc + +/* + * ZLint Copyright 2021 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 the License for the specific language governing + * permissions and limitations under the License. + */ + +import ( + "github.com/zmap/zcrypto/x509" + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/util" +) + +type crlHasNextUpdate struct{} + +/************************************************ +RFC 5280: 5.1.2.5 +Conforming CRL issuers MUST include the nextUpdate field in all CRLs. +************************************************/ + +func init() { + lint.RegisterRevocationListLint(&lint.RevocationListLint{ + LintMetadata: lint.LintMetadata{ + Name: "e_crl_has_next_update", + Description: "Conforming CRL issuers MUST include the nextUpdate field in all CRLs.", + Citation: "RFC 5280: 5.1.2.5", + Source: lint.RFC5280, + EffectiveDate: util.RFC5280Date, + }, + Lint: NewCrlHasNextUpdate, + }) +} + +func NewCrlHasNextUpdate() lint.RevocationListLintInterface { + return &crlHasNextUpdate{} +} + +func (l *crlHasNextUpdate) CheckApplies(c *x509.RevocationList) bool { + return true +} + +func (l *crlHasNextUpdate) Execute(c *x509.RevocationList) *lint.LintResult { + if c.NextUpdate.IsZero() { + return &lint.LintResult{Status: lint.Error, Details: "Confoming CRL issuers MUST include the nextUpdate field in all CRLs."} + } + return &lint.LintResult{Status: lint.Pass} +} diff --git a/v3/lints/rfc/lint_crl_has_next_update_test.go b/v3/lints/rfc/lint_crl_has_next_update_test.go new file mode 100644 index 000000000..717610478 --- /dev/null +++ b/v3/lints/rfc/lint_crl_has_next_update_test.go @@ -0,0 +1,42 @@ +package rfc + +import ( + "testing" + + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/test" +) + +/* + * ZLint Copyright 2021 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 the License for the specific language governing + * permissions and limitations under the License. + */ + +func TestCrlHasNextUpdate(t *testing.T) { + inputPath := "crlHasNextUpdate.pem" + want := lint.Pass + got := test.TestRevocationListLint(t, "e_crl_has_next_update", inputPath).Status + + if want != got { + t.Errorf("%s: expected %s, got %s", inputPath, want, got) + } +} + +func TestCrlNotHaveNextUpdate(t *testing.T) { + inputPath := "crlNotHaveNextUpdate.pem" + want := lint.Error + got := test.TestRevocationListLint(t, "e_crl_has_next_update", inputPath).Status + + if want != got { + t.Errorf("%s: expected %s, got %s", inputPath, want, got) + } +} diff --git a/v3/resultset.go b/v3/resultset.go index 0fb3c7594..e1f70a329 100644 --- a/v3/resultset.go +++ b/v3/resultset.go @@ -31,15 +31,28 @@ type ResultSet struct { FatalsPresent bool `json:"fatals_present"` } -// Execute lints the given certificate with all of the lints in the provided +// Execute lints on the given certificate with all of the lints in the provided // registry. The ResultSet is mutated to trace the lint results obtained from // linting the certificate. -func (z *ResultSet) execute(cert *x509.Certificate, registry lint.Registry) { +func (z *ResultSet) executeCertificate(o *x509.Certificate, registry lint.Registry) { + z.Results = make(map[string]*lint.LintResult, len(registry.Names())) + // Run each lint from the registry. + for _, lint := range registry.CertificateLints().Lints() { + res := lint.Execute(o, registry.GetConfiguration()) + z.Results[lint.Name] = res + z.updateErrorStatePresent(res) + } +} + +// Execute lints on the given CRL with all of the lints in the provided +// registry. The ResultSet is mutated to trace the lint results obtained from +// linting the CRL. +func (z *ResultSet) executeRevocationList(o *x509.RevocationList, registry lint.Registry) { z.Results = make(map[string]*lint.LintResult, len(registry.Names())) // Run each lints from the registry. - for _, name := range registry.Names() { - res := registry.ByName(name).Execute(cert, registry.GetConfiguration()) - z.Results[name] = res + for _, lint := range registry.RevocationListLints().Lints() { + res := lint.Execute(o, registry.GetConfiguration()) + z.Results[lint.Name] = res z.updateErrorStatePresent(res) } } diff --git a/v3/test/helpers.go b/v3/test/helpers.go index 12d99adee..aea157e60 100644 --- a/v3/test/helpers.go +++ b/v3/test/helpers.go @@ -20,6 +20,7 @@ import ( "encoding/pem" "fmt" "os" + "testing" "strings" @@ -48,6 +49,25 @@ func TestLintWithConfig(lintName string, testCertFilename string, configuration return TestLintCert(lintName, ReadTestCert(testCertFilename), config) } +// TestRevocationListLint executes the given lintName against a CRL read from +// a testcrl data file with the given filename. Filenames should be relative to +// `testdata/` and not absolute file paths. +// +//nolint:revive +func TestRevocationListLint(tb testing.TB, lintName string, testCRLFilename string) *lint.LintResult { + tb.Helper() + return TestRevocationListLintWithConfig(tb, lintName, testCRLFilename, "") +} + +func TestRevocationListLintWithConfig(tb testing.TB, lintName string, testCRLFilename string, configuration string) *lint.LintResult { + tb.Helper() + config, err := lint.NewConfigFromString(configuration) + if err != nil { + tb.Fatal(err) + } + return TestLintRevocationList(tb, lintName, ReadTestRevocationList(tb, testCRLFilename), config) +} + // TestLintCert executes a lint with the given name against an already parsed // certificate. This is useful when a unit test reads a certificate from disk // and then mutates it in some way before trying to lint it. @@ -57,7 +77,7 @@ func TestLintWithConfig(lintName string, testCertFilename string, configuration // //nolint:revive func TestLintCert(lintName string, cert *x509.Certificate, ctx lint.Configuration) *lint.LintResult { - l := lint.GlobalRegistry().ByName(lintName) + l := lint.GlobalRegistry().CertificateLints().ByName(lintName) if l == nil { panic(fmt.Sprintf( "Lint name %q does not exist in lint.Lints. "+ @@ -74,6 +94,30 @@ func TestLintCert(lintName string, cert *x509.Certificate, ctx lint.Configuratio return res } +// TestLintRevocationList executes a lint with the given name against an already parsed +// revocation list. This is useful when a unit test reads a revocation list from disk +// and then mutates it in some way before trying to lint it. +// +//nolint:revive +func TestLintRevocationList(tb testing.TB, lintName string, crl *x509.RevocationList, ctx lint.Configuration) *lint.LintResult { + tb.Helper() + l := lint.GlobalRegistry().RevocationListLints().ByName(lintName) + if l == nil { + tb.Fatalf( + "Lint name %q does not exist in lint.Lints. "+ + "Did you forget to RegisterLint?\n", + lintName) + } + res := l.Execute(crl, ctx) + // We never expect a lint to return a nil LintResult + if res == nil { + tb.Fatalf( + "Running lint %q on test revocation list generated a nil LintResult.\n", + lintName) + } + return res +} + // ReadTestCert loads a x509.Certificate from the given inPath which is assumed // to be relative to `testdata/`. // @@ -110,3 +154,41 @@ func ReadTestCert(inPath string) *x509.Certificate { return theCert } + +// ReadTestRevocationList loads a x509.RevocationList from the given inPath which is assumed +// to be relative to `testdata/`. +// +// Important: ReadTestRevocationList is only appropriate for unit tests. It will panic if +// the inPath file can not be loaded. +func ReadTestRevocationList(tb testing.TB, inPath string) *x509.RevocationList { + tb.Helper() + fullPath := fmt.Sprintf("../../testdata/%s", inPath) + data, err := os.ReadFile(fullPath) + if err != nil { + tb.Fatalf( + "Unable to read test revocation list from %q - %q "+ + "Does a unit test have an incorrect test file name?\n", + fullPath, err) + } + + if strings.Contains(string(data), "-BEGIN X509 CRL-") { + block, _ := pem.Decode(data) + if block == nil { //nolint: staticcheck // tb.Fatalf exits + tb.Fatalf( + "Failed to PEM decode test revocation list from %q - "+ + "Does a unit test have a buggy test cert file?\n", + fullPath) + } + data = block.Bytes //nolint: staticcheck // tb.Fatalf exits + } + + theCrl, err := x509.ParseRevocationList(data) + if err != nil { + tb.Fatalf( + "Failed to parse x509 test certificate from %q - %q "+ + "Does a unit test have a buggy test cert file?\n", + fullPath, err) + } + + return theCrl +} diff --git a/v3/testdata/crlHasNextUpdate.pem b/v3/testdata/crlHasNextUpdate.pem new file mode 100644 index 000000000..fb236980a --- /dev/null +++ b/v3/testdata/crlHasNextUpdate.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBnjCBhwIBATANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDEw1BbWlyIHdhcyBI +ZXJlFw0yMzAzMTMwNTUyNTVaFw0yMzAzMTQwNTUyNTVaoDswOTArBgNVHSMEJDAi +gCAywvCJz28KsE/6Wf9E1nuiihBFWlUyq7X/RDgn5SllIDAKBgNVHRQEAwIBATAN +BgkqhkiG9w0BAQsFAAOCAQEAakioBhLs31svWHGmolDhUg6O1daN6zXSAz/avgzl +38aTKfRSNQ+vM7qgrvCoRojnamziJgXe1hz+/dc8H0/+WEBwVgp1rBzr8f25dSZC +lXBHT1cNI5RL+wU0pFMouUiwWqwUg8o9iGYkqvhuko4AQIcpAoBuf0OggjCuj48r +FX7UN7Kz4pc/4ufengKGkf7EeEQffY3zlS0DAtWv+exoQ6Dt+otDr0PbINJZg+46 +TJ/+0w6RsLGoe4Sh/PYPfaCngMyezENUgJgR1+vF6hbVUweeOB+4nFRNxvHMup0G +GEA4yfzQtHWL8rizWUCyuqXEMPZLzyJT0rv5cLgoOvs+8Q== +-----END X509 CRL----- diff --git a/v3/testdata/crlNotHaveNextUpdate.pem b/v3/testdata/crlNotHaveNextUpdate.pem new file mode 100644 index 000000000..ae95454da --- /dev/null +++ b/v3/testdata/crlNotHaveNextUpdate.pem @@ -0,0 +1,11 @@ +-----BEGIN X509 CRL----- +MIIBjjB4AgEBMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNVBAMTDUFtaXIgd2FzIEhl +cmUXDTIzMDMxMzA1NTQwOFqgOzA5MCsGA1UdIwQkMCKAIKiGvOMhlD6FiuwaEDl+ +FxP5fyorz7E9iDke1/q+ngvkMAoGA1UdFAQDAgEBMA0GCSqGSIb3DQEBCwUAA4IB +AQAWq81ZR98KCw3Y3KiH2ShJ+mxlgYO91ovQfzsbCOSHrcV9bnVYG8k3WMWBen/v +LsXiSaVeG+9G1b459KuB6yVv24N0vtpzXOorFR1oi0wPWtYzPhkT+RD2Ov10XO2G +bk3DSwcqcjYqx1Hu1BlHzEyTUvwij6XWUx1uc+olH6scRmycn9yGBMSga/Xgx6g1 +4yM9lzN8lHeN2JLr1vnu///iBwwPvdhPMzUE0n/smH/6bkkZXHM33s0cJ6Wm0bLg +TUg9QKGR2PIehZvJg1vvhpZyIEnpGPp1hN9FsK8eKuMJWEEqP7s5URHaHNYlmagA +ylcX526EcfmL6vqtz5OIsfNC +-----END X509 CRL----- diff --git a/v3/zlint.go b/v3/zlint.go index 56e1d405e..fea16c8bd 100644 --- a/v3/zlint.go +++ b/v3/zlint.go @@ -55,7 +55,35 @@ func LintCertificateEx(c *x509.Certificate, registry lint.Registry) *ResultSet { registry = lint.GlobalRegistry() } res := new(ResultSet) - res.execute(c, registry) + res.executeCertificate(c, registry) + res.Version = Version + res.Timestamp = time.Now().Unix() + return res +} + +// LintRevocationList runs all registered lints on r using default options, +// producing a ResultSet. +// +// Using LintRevocationList(r) is equivalent to calling LintRevocationListEx(r, nil). +func LintRevocationList(r *x509.RevocationList) *ResultSet { + return LintRevocationListEx(r, nil) +} + +// LintRevocationListEx runs lints from the provided registry on r producing +// a ResultSet. Providing an explicit registry allows the caller to filter the +// lints that will be run. (See lint.Registry.Filter()) +// +// If registry is nil then the global registry of all lints is used and this +// function is equivalent to calling LintRevocationListEx(r). +func LintRevocationListEx(r *x509.RevocationList, registry lint.Registry) *ResultSet { + if r == nil { + return nil + } + if registry == nil { + registry = lint.GlobalRegistry() + } + res := new(ResultSet) + res.executeRevocationList(r, registry) res.Version = Version res.Timestamp = time.Now().Unix() return res