diff --git a/v3/lints/cabf_ev/lint_ev_certificate_policies.go b/v3/lints/cabf_ev/lint_ev_certificate_policies.go new file mode 100644 index 000000000..224c4b04d --- /dev/null +++ b/v3/lints/cabf_ev/lint_ev_certificate_policies.go @@ -0,0 +1,79 @@ +/* + * ZLint Copyright 2024 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 cabf_ev + +import ( + "fmt" + "net/url" + "time" + + "github.com/zmap/zcrypto/x509" + + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/util" +) + +func init() { + lint.RegisterCertificateLint(&lint.CertificateLint{ + LintMetadata: lint.LintMetadata{ + Name: "e_ev_certificate_policies", + Description: "EV Certificates issued to Subscribers MUST include a CPS URI policy qualifier", + Citation: "CA/Browser Forum EV Guidelines v1.8.1, Sec. 9.7.3", + Source: lint.CABFEVGuidelines, + EffectiveDate: util.CABFEV_1_2, + }, + Lint: NewEvTechnicalRequirements, + }) +} + +type EvCertificatePolicies struct{} + +func NewEvTechnicalRequirements() lint.LintInterface { + return &EvCertificatePolicies{} +} + +func (l *EvCertificatePolicies) CheckApplies(c *x509.Certificate) bool { + return c.ValidationLevel == x509.EV +} + +func (l *EvCertificatePolicies) Execute(c *x509.Certificate) *lint.LintResult { + for _, uris := range c.CPSuri { + if uris != nil { + // Policy i is the CPS URI. + // c.CPSuri[i] is populated only if the policyQualifierID is the correct { id-qt 1 } + if len(uris) != 1 { + return &lint.LintResult{Status: lint.Error} + } + uri := uris[0] + + // The CPS URI is a "HTTP URL for the Root CA’s Certification Practice Statement" + cps, err := url.Parse(uri) + if err != nil { + return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("Error parsing CPS URI: %v", err)} + } + if cps.Scheme != "http" && cps.Scheme != "https" { + return &lint.LintResult{Status: lint.Error, Details: fmt.Sprintf("CPS URI scheme not http(s): %s", cps.Scheme)} + } + + if !util.HasValidTLD(cps.Hostname(), time.Now()) { + return &lint.LintResult{Status: lint.Error} + } + + return &lint.LintResult{Status: lint.Pass} + } + } + + return &lint.LintResult{Status: lint.Error} +} diff --git a/v3/lints/cabf_ev/lint_ev_certificate_policies_test.go b/v3/lints/cabf_ev/lint_ev_certificate_policies_test.go new file mode 100644 index 000000000..554bd92af --- /dev/null +++ b/v3/lints/cabf_ev/lint_ev_certificate_policies_test.go @@ -0,0 +1,50 @@ +/* + * ZLint Copyright 2024 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 cabf_ev + +import ( + "testing" + + "github.com/zmap/zlint/v3/lint" + "github.com/zmap/zlint/v3/test" +) + +func TestEvCertificatePolicies(t *testing.T) { + tests := []struct { + inputPath string + expected lint.LintStatus + }{ + { + inputPath: "evAllGood.pem", + expected: lint.Pass, + }, + { + inputPath: "evNoCPSURI.pem", + expected: lint.Error, + }, + { + inputPath: "dnsNameValidTLD.pem", + expected: lint.NA, + }, + } + for _, tt := range tests { + t.Run(tt.inputPath, func(t *testing.T) { + out := test.TestLint("e_ev_certificate_policies", tt.inputPath) + if out.Status != tt.expected { + t.Errorf("%s: expected %s, got %s", tt.inputPath, tt.expected, out.Status) + } + }) + } +} diff --git a/v3/testdata/evNoCPSURI.pem b/v3/testdata/evNoCPSURI.pem new file mode 100644 index 000000000..1f0aa0683 --- /dev/null +++ b/v3/testdata/evNoCPSURI.pem @@ -0,0 +1,167 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 32:2f:2e:ee:d4:19:ab:b5:f6:f9:ce:92:08:c2:e3:90 + Signature Algorithm: sha256WithRSAEncryption + Issuer: C = US, O = "Entrust, Inc.", OU = See www.entrust.net/legal-terms, OU = "(c) 2014 Entrust, Inc. - for authorized use only", CN = Entrust Certification Authority - L1M + Validity + Not Before: Feb 8 22:19:05 2024 GMT + Not After : Feb 8 22:19:04 2025 GMT + Subject: C = US, ST = Minnesota, L = Shakopee, jurisdictionC = US, jurisdictionST = Delaware, O = Entrust Corporation, businessCategory = Private Organization, serialNumber = 705421, CN = entrust.com + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:dd:8d:51:bb:e1:d9:b2:dd:50:41:8a:7f:33:2c: + 0f:37:f0:68:c6:3b:ec:47:12:7e:97:35:4a:8a:03: + 20:a2:69:ca:d4:5f:ff:07:51:6d:13:ec:73:24:e7: + 56:19:26:fd:d9:3f:82:78:87:28:61:03:e3:da:c7: + 1e:38:50:8b:d2:11:f4:66:e8:ea:79:c0:32:24:74: + 39:b8:1b:42:aa:24:e4:71:e4:9f:78:38:5a:c7:8e: + f3:bc:b4:c8:2d:a4:86:97:75:65:3e:04:28:28:87: + d4:57:f6:ac:8a:86:41:e3:5a:5a:28:b1:8c:d2:8a: + 3d:d6:a6:97:61:7e:00:4a:95:4d:a6:1b:31:12:94: + 51:05:9b:8c:b5:4a:fb:2e:95:a2:b9:f6:56:9e:44: + 8e:d0:55:60:f8:fd:ac:70:49:86:5b:19:0f:04:c2: + 06:32:0d:5a:e1:6c:b8:d0:e6:7a:2f:0d:a5:60:12: + d4:32:e0:5b:03:29:b7:2e:96:aa:f7:e1:cd:f3:10: + 7d:8c:a7:85:be:ca:9e:6c:13:9c:77:7c:17:02:43: + bd:37:40:68:9f:27:b9:a5:ba:b4:1a:51:bf:8e:53: + 49:c5:29:b4:7b:e3:13:d2:73:ce:37:74:3a:80:68: + 86:73:28:c6:df:05:7a:91:be:ee:ba:e0:58:7a:f3: + 91:21 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: critical + CA:FALSE + X509v3 Subject Key Identifier: + 80:23:9A:50:45:09:5B:3C:2D:00:98:1C:50:EF:E0:79:4B:9A:C9:7A + X509v3 Authority Key Identifier: + C3:F7:D0:B5:2A:30:AD:AF:0D:91:21:70:39:54:DD:BC:89:70:C7:3A + Authority Information Access: + OCSP - URI:http://ocsp.entrust.net + CA Issuers - URI:http://aia.entrust.net/l1m-chain256.cer + X509v3 CRL Distribution Points: + Full Name: + URI:http://crl.entrust.net/level1m.crl + X509v3 Subject Alternative Name: + DNS:entrust.com, DNS:www.entrust.com, DNS:www.sumaidmzprx.entrust.com, DNS:www.kiteworks.entrust.com, DNS:www.drupal.entrust.com, DNS:web.entrust.com, DNS:w2.entrust.com, DNS:translate.entrust.com, DNS:testtranslate.entrust.com, DNS:testonline.entrust.com, DNS:test.entrust.com, DNS:sumaidmzprx.entrust.com, DNS:plm.entrust.com, DNS:outlook.entrust.com, DNS:new.entrust.com, DNS:mail.entrust.com, DNS:licensing.entrust.com, DNS:kiteworks.entrust.com, DNS:in.entrust.com, DNS:firmwarestg.entrust.com, DNS:firmwaredev.entrust.com, DNS:firmware.entrust.com, DNS:drupalw.entrust.com, DNS:drupalstgw.entrust.com, DNS:drupalstg.entrust.com, DNS:drupaldevw.entrust.com, DNS:drupaldev.entrust.com, DNS:drupal.entrust.com, DNS:china-proxy.entrust.com, DNS:china-proxy-test.entrust.com, DNS:autodiscover.entrust.com, DNS:adp.entrust.com, DNS:activation.licensing.entrust.com, DNS:wd.entrust.com, DNS:fd.entrust.com, DNS:ws.entrust.com, DNS:fs.entrust.com, DNS:wdw.entrust.com, DNS:wsw.entrust.com, DNS:plmtest.entrust.com + X509v3 Key Usage: critical + Digital Signature, Key Encipherment + X509v3 Extended Key Usage: + TLS Web Server Authentication, TLS Web Client Authentication + X509v3 Certificate Policies: + Policy: 2.23.140.1.1 + Policy: 2.16.840.1.114028.10.1.2 + CT Precertificate SCTs: + Signed Certificate Timestamp: + Version : v1 (0x0) + Log ID : E6:D2:31:63:40:77:8C:C1:10:41:06:D7:71:B9:CE:C1: + D2:40:F6:96:84:86:FB:BA:87:32:1D:FD:1E:37:8E:50 + Timestamp : Feb 8 22:19:05.976 2024 GMT + Extensions: none + Signature : ecdsa-with-SHA256 + 30:45:02:20:09:32:16:2C:C2:4A:7E:82:17:FF:FA:71: + 7D:BF:6A:CF:40:84:E6:1A:78:F4:54:D9:72:86:5F:96: + CC:54:61:D4:02:21:00:F2:A5:81:FB:A7:AF:A4:5A:B7: + 9C:F0:57:E4:A1:6E:CE:EE:19:86:B0:3F:C5:07:85:3C: + 41:80:6B:99:35:76:4D + Signed Certificate Timestamp: + Version : v1 (0x0) + Log ID : A2:E3:0A:E4:45:EF:BD:AD:9B:7E:38:ED:47:67:77:53: + D7:82:5B:84:94:D7:2B:5E:1B:2C:C4:B9:50:A4:47:E7 + Timestamp : Feb 8 22:19:06.005 2024 GMT + Extensions: none + Signature : ecdsa-with-SHA256 + 30:44:02:20:3F:EC:E9:63:68:1D:0E:03:CA:54:64:2C: + AF:C8:D4:D6:7B:02:02:64:C5:81:48:BF:83:0F:08:A3: + D9:06:B8:D4:02:20:31:20:52:17:E5:B6:13:66:39:79: + 1F:B0:C1:CE:A4:F2:FA:EB:CB:CD:2B:53:75:78:1C:77: + 92:A6:3B:CD:E5:FE + Signed Certificate Timestamp: + Version : v1 (0x0) + Log ID : 4E:75:A3:27:5C:9A:10:C3:38:5B:6C:D4:DF:3F:52:EB: + 1D:F0:E0:8E:1B:8D:69:C0:B1:FA:64:B1:62:9A:39:DF + Timestamp : Feb 8 22:19:05.990 2024 GMT + Extensions: none + Signature : ecdsa-with-SHA256 + 30:45:02:20:18:59:3B:1E:78:40:18:53:84:07:E0:71: + 25:2E:6F:C1:45:32:29:DC:73:03:E1:ED:F3:23:28:7F: + E6:5C:BD:E6:02:21:00:9B:9A:0F:5C:67:25:69:F6:A4: + 3E:AD:FB:82:BD:16:E4:77:A9:1E:01:4A:96:14:3D:DF: + 94:0D:5B:B7:87:11:44 + Signature Algorithm: sha256WithRSAEncryption + Signature Value: + 2b:73:4c:31:7f:80:54:56:85:ed:2b:f6:05:ad:70:0d:3a:6d: + fe:e8:b6:46:d9:ee:fc:02:8f:4c:64:fb:cc:f9:0c:b6:74:41: + e3:b8:ca:72:c8:2d:5b:56:d8:eb:85:1a:09:36:6d:92:79:ce: + 3a:03:d3:ec:12:e6:89:f5:44:d6:ab:9d:bd:90:d6:d1:c7:5c: + 83:d4:1e:7b:47:97:99:09:5e:8e:57:35:99:fb:6a:dd:05:9c: + e6:39:a4:7c:a3:6f:ea:5f:a7:31:bf:90:09:33:26:6b:49:d5: + 5d:a6:e9:f6:9a:bb:85:29:b0:df:7e:be:1d:6f:6a:ba:b0:c8: + 94:ae:fe:da:93:59:4a:6c:d8:fb:e8:da:8b:17:b0:09:49:ce: + a4:25:61:f2:67:05:d0:6d:00:56:cf:d1:33:0f:28:a1:79:10: + db:17:56:d6:cb:9e:b4:28:8b:cf:c1:eb:c7:e7:ee:9b:a8:e7: + 08:2e:d8:6b:87:80:d9:22:2b:72:58:91:8e:c8:5b:38:4b:0d: + 05:98:9d:4e:9b:69:f0:b1:fe:3c:57:4d:2c:d2:e1:da:b2:53: + b5:fc:5c:d0:06:df:9b:9e:10:71:c0:21:a1:6d:cf:e5:71:53: + c0:cc:b8:82:1f:de:46:d3:18:cb:c4:9c:e6:97:d8:e6:81:7c: + 50:d4:d6:13 +-----BEGIN CERTIFICATE----- +MIIKOTCCCSGgAwIBAgIQMi8u7tQZq7X2+c6SCMLjkDANBgkqhkiG9w0BAQsFADCB +ujELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsT +H1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAy +MDE0IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEuMCwG +A1UEAxMlRW50cnVzdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEwxTTAeFw0y +NDAyMDgyMjE5MDVaFw0yNTAyMDgyMjE5MDRaMIHIMQswCQYDVQQGEwJVUzESMBAG +A1UECBMJTWlubmVzb3RhMREwDwYDVQQHEwhTaGFrb3BlZTETMBEGCysGAQQBgjc8 +AgEDEwJVUzEZMBcGCysGAQQBgjc8AgECEwhEZWxhd2FyZTEcMBoGA1UEChMTRW50 +cnVzdCBDb3Jwb3JhdGlvbjEdMBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24x +DzANBgNVBAUTBjcwNTQyMTEUMBIGA1UEAxMLZW50cnVzdC5jb20wggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdjVG74dmy3VBBin8zLA838GjGO+xHEn6X +NUqKAyCiacrUX/8HUW0T7HMk51YZJv3ZP4J4hyhhA+Paxx44UIvSEfRm6Op5wDIk +dDm4G0KqJORx5J94OFrHjvO8tMgtpIaXdWU+BCgoh9RX9qyKhkHjWloosYzSij3W +ppdhfgBKlU2mGzESlFEFm4y1SvsulaK59laeRI7QVWD4/axwSYZbGQ8EwgYyDVrh +bLjQ5novDaVgEtQy4FsDKbculqr34c3zEH2Mp4W+yp5sE5x3fBcCQ703QGifJ7ml +urQaUb+OU0nFKbR74xPSc843dDqAaIZzKMbfBXqRvu664Fh685EhAgMBAAGjggYp +MIIGJTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBSAI5pQRQlbPC0AmBxQ7+B5S5rJ +ejAfBgNVHSMEGDAWgBTD99C1KjCtrw2RIXA5VN28iXDHOjBoBggrBgEFBQcBAQRc +MFowIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLmVudHJ1c3QubmV0MDMGCCsGAQUF +BzAChidodHRwOi8vYWlhLmVudHJ1c3QubmV0L2wxbS1jaGFpbjI1Ni5jZXIwMwYD +VR0fBCwwKjAooCagJIYiaHR0cDovL2NybC5lbnRydXN0Lm5ldC9sZXZlbDFtLmNy +bDCCA2IGA1UdEQSCA1kwggNVggtlbnRydXN0LmNvbYIPd3d3LmVudHJ1c3QuY29t +ght3d3cuc3VtYWlkbXpwcnguZW50cnVzdC5jb22CGXd3dy5raXRld29ya3MuZW50 +cnVzdC5jb22CFnd3dy5kcnVwYWwuZW50cnVzdC5jb22CD3dlYi5lbnRydXN0LmNv +bYIOdzIuZW50cnVzdC5jb22CFXRyYW5zbGF0ZS5lbnRydXN0LmNvbYIZdGVzdHRy +YW5zbGF0ZS5lbnRydXN0LmNvbYIWdGVzdG9ubGluZS5lbnRydXN0LmNvbYIQdGVz +dC5lbnRydXN0LmNvbYIXc3VtYWlkbXpwcnguZW50cnVzdC5jb22CD3BsbS5lbnRy +dXN0LmNvbYITb3V0bG9vay5lbnRydXN0LmNvbYIPbmV3LmVudHJ1c3QuY29tghBt +YWlsLmVudHJ1c3QuY29tghVsaWNlbnNpbmcuZW50cnVzdC5jb22CFWtpdGV3b3Jr +cy5lbnRydXN0LmNvbYIOaW4uZW50cnVzdC5jb22CF2Zpcm13YXJlc3RnLmVudHJ1 +c3QuY29tghdmaXJtd2FyZWRldi5lbnRydXN0LmNvbYIUZmlybXdhcmUuZW50cnVz +dC5jb22CE2RydXBhbHcuZW50cnVzdC5jb22CFmRydXBhbHN0Z3cuZW50cnVzdC5j +b22CFWRydXBhbHN0Zy5lbnRydXN0LmNvbYIWZHJ1cGFsZGV2dy5lbnRydXN0LmNv +bYIVZHJ1cGFsZGV2LmVudHJ1c3QuY29tghJkcnVwYWwuZW50cnVzdC5jb22CF2No +aW5hLXByb3h5LmVudHJ1c3QuY29tghxjaGluYS1wcm94eS10ZXN0LmVudHJ1c3Qu +Y29tghhhdXRvZGlzY292ZXIuZW50cnVzdC5jb22CD2FkcC5lbnRydXN0LmNvbYIg +YWN0aXZhdGlvbi5saWNlbnNpbmcuZW50cnVzdC5jb22CDndkLmVudHJ1c3QuY29t +gg5mZC5lbnRydXN0LmNvbYIOd3MuZW50cnVzdC5jb22CDmZzLmVudHJ1c3QuY29t +gg93ZHcuZW50cnVzdC5jb22CD3dzdy5lbnRydXN0LmNvbYITcGxtdGVzdC5lbnRy +dXN0LmNvbTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG +AQUFBwMCMCAGA1UdIAQZMBcwBwYFZ4EMAQEwDAYKYIZIAYb6bAoBAjCCAX0GCisG +AQQB1nkCBAIEggFtBIIBaQFnAHYA5tIxY0B3jMEQQQbXcbnOwdJA9paEhvu6hzId +/R43jlAAAAGNis2XeAAABAMARzBFAiAJMhYswkp+ghf/+nF9v2rPQITmGnj0VNly +hl+WzFRh1AIhAPKlgfunr6Rat5zwV+Shbs7uGYawP8UHhTxBgGuZNXZNAHUAouMK +5EXvva2bfjjtR2d3U9eCW4SU1yteGyzEuVCkR+cAAAGNis2XlQAABAMARjBEAiA/ +7OljaB0OA8pUZCyvyNTWewICZMWBSL+DDwij2Qa41AIgMSBSF+W2E2Y5eR+wwc6k +8vrry80rU3V4HHeSpjvN5f4AdgBOdaMnXJoQwzhbbNTfP1LrHfDgjhuNacCx+mSx +Ypo53wAAAY2KzZeGAAAEAwBHMEUCIBhZOx54QBhThAfgcSUub8FFMinccwPh7fMj +KH/mXL3mAiEAm5oPXGclafakPq37gr0W5HepHgFKlhQ935QNW7eHEUQwDQYJKoZI +hvcNAQELBQADggEBACtzTDF/gFRWhe0r9gWtcA06bf7otkbZ7vwCj0xk+8z5DLZ0 +QeO4ynLILVtW2OuFGgk2bZJ5zjoD0+wS5on1RNarnb2Q1tHHXIPUHntHl5kJXo5X +NZn7at0FnOY5pHyjb+pfpzG/kAkzJmtJ1V2m6faau4UpsN9+vh1varqwyJSu/tqT +WUps2Pvo2osXsAlJzqQlYfJnBdBtAFbP0TMPKKF5ENsXVtbLnrQoi8/B68fn7puo +5wgu2GuHgNkiK3JYkY7IWzhLDQWYnU6bafCx/jxXTSzS4dqyU7X8XNAG35ueEHHA +IaFtz+VxU8DMuIIf3kbTGMvEnOaX2OaBfFDU1hM= +-----END CERTIFICATE----- diff --git a/v3/util/time.go b/v3/util/time.go index 0f3a1948c..7350e844e 100644 --- a/v3/util/time.go +++ b/v3/util/time.go @@ -89,6 +89,7 @@ var ( ) var ( + CABFEV_1_2 = time.Date(2008, time.April, 10, 0, 0, 0, 0, time.UTC) CABFEV_9_8_2 = CABV170Date )