├── testdata ├── metadata.pem.golden ├── cat.pem.golden ├── invalid_encoding2.pem.golden ├── nokeyusage.pem.golden ├── ivissues.pem.golden ├── invalid_encoding1.pem.golden ├── 512bit.pem.golden ├── 2043bit.pem.golden ├── exponent1.pem.golden ├── 1024cert.pem.golden ├── evissues2.pem.golden ├── evissues.pem.golden ├── gtenonsan.pem.golden ├── kuandcnnotinsan.pem.golden ├── notv3_2.pem.golden ├── notv3.pem.golden ├── notv3.pem ├── notv3_2.pem ├── gtenonsan.pem ├── 512bit.pem ├── 1024cert.pem ├── cat.pem ├── nokeyusage.pem ├── ivissues.pem ├── invalid_encoding2.pem ├── metadata.pem ├── evissues.pem ├── 2043bit.pem ├── invalid_encoding1.pem ├── exponent1.pem ├── kuandcnnotinsan.pem └── evissues2.pem ├── .travis.yml ├── errors ├── priority_string.go ├── errors_test.go └── errors.go ├── checks ├── certificate │ ├── internal │ │ ├── dnsnames.go │ │ ├── internal_test.go │ │ ├── ipaddress.go │ │ └── internal.go │ ├── version │ │ └── version.go │ ├── extensions │ │ └── extensions.go │ ├── publicsuffix │ │ ├── publicsuffix_test.go │ │ └── publicsuffix.go │ ├── issuerdn │ │ └── issuerdn.go │ ├── publickey │ │ ├── publickey.go │ │ └── goodkey │ │ │ ├── goodkey.go │ │ │ └── LICENSE.txt │ ├── keyusage │ │ ├── keyusagestring.go │ │ └── keyusage.go │ ├── basicconstraints │ │ └── basicconstraints.go │ ├── subject │ │ ├── subject_test.go │ │ ├── oid.go │ │ └── subject.go │ ├── all │ │ └── all.go │ ├── wildcard │ │ └── wildcard.go │ ├── signaturealgorithm │ │ └── signaturealgorithm.go │ ├── serialnumber │ │ └── serialnumber.go │ ├── subjectaltname │ │ ├── subjectaltname_test.go │ │ └── subjectaltname.go │ ├── validity │ │ └── validity.go │ ├── aiaissuers │ │ └── aiaissuers.go │ ├── extkeyusage │ │ └── extkeyusage.go │ └── revocation │ │ └── revocation.go ├── extensions │ ├── subjectkeyid │ │ └── subjectkeyid.go │ ├── authoritykeyid │ │ └── authoritykeyid.go │ ├── subjectaltname │ │ └── subjectaltname.go │ ├── adobetimestamp │ │ └── adobetimestamp.go │ ├── pdfrevocation │ │ └── pdfrevocation.go │ ├── crldistributionpoints │ │ └── crldistributionpoints.go │ ├── smimecapabilities │ │ └── smimecapabilities.go │ ├── keyusage │ │ └── keyusage.go │ ├── ct │ │ └── ct.go │ ├── ocspnocheck │ │ └── ocspnocheck.go │ ├── authorityinfoaccess │ │ └── authorityinfoaccess.go │ ├── nameconstraints │ │ └── nameconstraints.go │ ├── all │ │ └── all.go │ ├── policyidentifiers │ │ └── policyidentifiers.go │ ├── extkeyusage │ │ └── extkeyusage.go │ ├── basicconstraints │ │ └── basicconstraints.go │ └── ocspmuststaple │ │ ├── ocspmuststaple.go │ │ └── ocspmuststaple_test.go ├── certificate.go ├── filter.go └── extensions.go ├── asn1 ├── format_test.go ├── asn1.go └── format.go ├── certdata ├── certificate.go ├── type.go └── policyoid.go ├── examples ├── ct │ └── ct.go └── specificchecks │ └── specificchecks.go ├── README.md ├── certlint_test.go ├── LICENSE.txt └── certlint.go /testdata/metadata.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: OV 2 | Certificate Errors: 2 3 | Priority: Error, Message: Forbidden value in UTF8String '_' 4 | Priority: Info, Message: commonName field is deprecated 5 | -------------------------------------------------------------------------------- /testdata/cat.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: - 2 | Certificate Errors: 2 3 | Priority: Error, Message: Forbidden value in PrintableString '-' 4 | Priority: Error, Message: Forbidden value in PrintableString '-' 5 | -------------------------------------------------------------------------------- /testdata/invalid_encoding2.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: EV 2 | Certificate Errors: 2 3 | Priority: Warning, Message: Using deprecated TeletexString for 'Düsseldorf' 4 | Priority: Info, Message: commonName field is deprecated 5 | -------------------------------------------------------------------------------- /testdata/nokeyusage.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: OV 2 | Certificate Errors: 3 3 | Priority: Error, Message: Certificate contains no extended key usage 4 | Priority: Error, Message: Certificate has no key usage set 5 | Priority: Info, Message: commonName field is deprecated 6 | -------------------------------------------------------------------------------- /testdata/ivissues.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: IV 2 | Certificate Errors: 3 3 | Priority: Warning, Message: Certificate contains unknown extension (2.5.29.18) 4 | Priority: Error, Message: localityName or stateOrProvinceName is required if organizationName is set 5 | Priority: Info, Message: commonName field is deprecated 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.11 5 | - "1.10" 6 | - master 7 | 8 | cache: 9 | directories: 10 | - $GOPATH/pkg 11 | 12 | before_install: 13 | - go get -t ./... 14 | 15 | script: 16 | - go test -race -coverprofile=coverage.txt -covermode=atomic 17 | 18 | after_success: 19 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /testdata/invalid_encoding1.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: EV 2 | Certificate Errors: 4 3 | Priority: Warning, Message: Using deprecated TeletexString for '上海市' 4 | Priority: Warning, Message: Using deprecated TeletexString for '上海市' 5 | Priority: Error, Message: Certificate contains an extended key usage different from ServerAuth, ClientAuth or ServerGatedCrypto 6 | Priority: Info, Message: commonName field is deprecated 7 | -------------------------------------------------------------------------------- /testdata/512bit.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: PS 2 | Certificate Errors: 5 3 | Priority: Error, Message: Certificate contains an extended key usage different from ClientAuth or EmailProtection 4 | Priority: Error, Message: Certificate key too small: 512 5 | Priority: Error, Message: Certificate contains a CRL with an non-preferred scheme (ldap) 6 | Priority: Info, Message: commonName field is deprecated 7 | Priority: Info, Message: emailAddress field is deprecated 8 | -------------------------------------------------------------------------------- /testdata/2043bit.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: DV 2 | Certificate Errors: 6 3 | Priority: Error, Message: KeyUsage extension SHOULD be marked as critical when present 4 | Priority: Warning, Message: Certificate contains unknown extension (2.5.29.18) 5 | Priority: Error, Message: Certificate has key usage KeyAgreement set 6 | Priority: Error, Message: Certificate key too small: 2043 7 | Priority: Info, Message: commonName field is deprecated 8 | Priority: Info, Message: emailAddress field is deprecated 9 | -------------------------------------------------------------------------------- /testdata/exponent1.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: OV 2 | Certificate Errors: 6 3 | Priority: Error, Message: KeyUsage extension SHOULD be marked as critical when present 4 | Priority: Warning, Message: Certificate contains unknown extension (2.5.29.18) 5 | Priority: Error, Message: Certificate has key usage KeyAgreement set 6 | Priority: Error, Message: Certificate key exponent should be odd and >2^16: 1 7 | Priority: Info, Message: commonName field is deprecated 8 | Priority: Info, Message: emailAddress field is deprecated 9 | -------------------------------------------------------------------------------- /testdata/1024cert.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: OV 2 | Certificate Errors: 6 3 | Priority: Error, Message: Certificate contains no extended key usage 4 | Priority: Error, Message: Certificate has key usage KeyAgreement set 5 | Priority: Error, Message: Certificate key too small: 1024 6 | Priority: Error, Message: Certificate contains a CRL with an non-preferred scheme (ldap) 7 | Priority: Error, Message: Certificate is using SHA1, but is still valid on/after 1 Jan 2017 8 | Priority: Info, Message: commonName field is deprecated 9 | -------------------------------------------------------------------------------- /errors/priority_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=Priority"; DO NOT EDIT. 2 | 3 | package errors 4 | 5 | import "strconv" 6 | 7 | const _Priority_name = "UnknownDebugInfoNoticeWarningErrorCriticalAlertEmergency" 8 | 9 | var _Priority_index = [...]uint8{0, 7, 12, 16, 22, 29, 34, 42, 47, 56} 10 | 11 | func (i Priority) String() string { 12 | if i < 0 || i >= Priority(len(_Priority_index)-1) { 13 | return "Priority(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _Priority_name[_Priority_index[i]:_Priority_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /testdata/evissues2.pem.golden: -------------------------------------------------------------------------------- 1 | Incomplete chain for CA de Certificados SSL EV www.manaria.eus 1d94f10d7dda98d257188b794b882346 &{[] 0 {0 0}} 2 | Processed Certificate Type: EV 3 | Certificate Errors: 5 4 | Priority: Error, Message: Certificate contains no Authority Info Access Issuers 5 | Priority: Warning, Message: Certificate contains unknown extension (2.5.29.18) 6 | Priority: Error, Message: localityName is required for EV certificates 7 | Priority: Error, Message: localityName or stateOrProvinceName is required if organizationName is set 8 | Priority: Info, Message: commonName field is deprecated 9 | -------------------------------------------------------------------------------- /checks/certificate/internal/dnsnames.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | psl "golang.org/x/net/publicsuffix" 8 | ) 9 | 10 | // All official domain suffixes are registered by icann, but because some 11 | // subdomains are not only check against the last part of the fqdn. 12 | func checkInternalName(fqdn string) bool { 13 | if ip := net.ParseIP(fqdn); ip != nil { 14 | return checkInternalIP(ip) 15 | } 16 | 17 | suffix := strings.Split(strings.ToLower(fqdn), ".") 18 | _, icann := psl.PublicSuffix(suffix[len(suffix)-1]) 19 | if icann { 20 | return false 21 | } 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /testdata/evissues.pem.golden: -------------------------------------------------------------------------------- 1 | Incomplete chain for VR IDENT EV SSL CA 2016 W1.DONNER.DE 68636c860bca0d94ab2be &{[] 0 {0 0}} 2 | Processed Certificate Type: EV 3 | Certificate Errors: 6 4 | Priority: Error, Message: Certificate contains no Authority Info Access Issuers 5 | Priority: Error, Message: businessCategory is required for EV certificates 6 | Priority: Error, Message: jurisdictionCountryName is required for EV certificates 7 | Priority: Error, Message: serialNumber is required for EV certificates 8 | Priority: Info, Message: commonName field is deprecated 9 | Priority: Error, Message: EV Certificate LifeTime exceeds 27 months 10 | -------------------------------------------------------------------------------- /asn1/format_test.go: -------------------------------------------------------------------------------- 1 | package asn1 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIsForbiddenString(t *testing.T) { 8 | // Some forbidden characters to test 9 | forbidden := []string{"-", "--", "_", "__", "-_", "- -", "?", "n/a", "N/A", ".", "+"} 10 | 11 | for _, v := range forbidden { 12 | if !isForbiddenString([]byte(v)) { 13 | t.Errorf("Forbidden string passed check: %q", v) 14 | } 15 | } 16 | 17 | // These could be accepted values 18 | accepted := []string{"OK+", "-OK"} 19 | 20 | for _, v := range accepted { 21 | if isForbiddenString([]byte(v)) { 22 | t.Errorf("Accepted string did not pass check: %q", v) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /testdata/gtenonsan.pem.golden: -------------------------------------------------------------------------------- 1 | Incomplete chain for Cybertrust Public SureServer SV CA *.whitehouse.gov 20000000001456be1a50e66883e &{[{4 Using deprecated TeletexString for '*.whitehouse.gov'}] 4 {0 0}} 2 | Processed Certificate Type: OV 3 | Certificate Errors: 5 4 | Priority: Warning, Message: Using deprecated TeletexString for '*.whitehouse.gov' 5 | Priority: Error, Message: Certificate contains no Authority Info Access Issuers 6 | Priority: Warning, Message: Certificate contains unknown extension (2.16.840.1.113730.1.1) 7 | Priority: Info, Message: commonName field is deprecated 8 | Priority: Error, Message: Certificate doesn't contain any subjectAltName 9 | -------------------------------------------------------------------------------- /testdata/kuandcnnotinsan.pem.golden: -------------------------------------------------------------------------------- 1 | Processed Certificate Type: EV 2 | Certificate Errors: 7 3 | Priority: Error, Message: Certificate has key usage KeyAgreement set 4 | Priority: Error, Message: businessCategory is required for EV certificates 5 | Priority: Error, Message: jurisdictionCountryName is required for EV certificates 6 | Priority: Info, Message: commonName field is deprecated 7 | Priority: Error, Message: Certificate CN is not listed in subjectAltName 8 | Priority: Error, Message: Certificate subjectAltName '*.passbyme.com' should not contain a wildcard 9 | Priority: Error, Message: Certificate subjectAltName '*.passbyme.net' should not contain a wildcard 10 | -------------------------------------------------------------------------------- /checks/certificate/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "github.com/globalsign/certlint/certdata" 5 | "github.com/globalsign/certlint/checks" 6 | "github.com/globalsign/certlint/errors" 7 | ) 8 | 9 | const checkName = "Certificate Version Check" 10 | 11 | func init() { 12 | checks.RegisterCertificateCheck(checkName, nil, Check) 13 | } 14 | 15 | // Check performs a strict verification on the extension according to the standard(s) 16 | func Check(d *certdata.Data) *errors.Errors { 17 | var e = errors.New(nil) 18 | 19 | if d.Cert.Version != 3 { 20 | e.Err("Certificate is not V3 (%d)", d.Cert.Version) 21 | return e 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /testdata/notv3_2.pem.golden: -------------------------------------------------------------------------------- 1 | Incomplete chain for Trustis Healthcare TT Issuing Authority *.bartshealth.nhs.uk a617a0c28282b8ab3398cbfb392c6a47 &{[] 0 {0 0}} 2 | Processed Certificate Type: OV 3 | Certificate Errors: 7 4 | Priority: Error, Message: Certificate contains no Authority Info Access Issuers 5 | Priority: Error, Message: Certificate contains no extended key usage 6 | Priority: Error, Message: Certificate has no key usage set 7 | Priority: Error, Message: Certificate contains no CRL or OCSP server 8 | Priority: Info, Message: commonName field is deprecated 9 | Priority: Error, Message: Certificate doesn't contain any subjectAltName 10 | Priority: Error, Message: Certificate is not V3 (1) 11 | -------------------------------------------------------------------------------- /checks/certificate/extensions/extensions.go: -------------------------------------------------------------------------------- 1 | package extensions 2 | 3 | import ( 4 | "github.com/globalsign/certlint/certdata" 5 | "github.com/globalsign/certlint/checks" 6 | "github.com/globalsign/certlint/errors" 7 | ) 8 | 9 | const checkName = "Extensions Check" 10 | 11 | func init() { 12 | checks.RegisterCertificateCheck(checkName, nil, Check) 13 | } 14 | 15 | // Check performs a strict verification on the extension according to the standard(s) 16 | func Check(d *certdata.Data) *errors.Errors { 17 | var e = errors.New(nil) 18 | for _, ext := range d.Cert.Extensions { 19 | // Check for any imported extensions and run all matching 20 | e.Append(checks.Extensions.Check(ext, d)) 21 | } 22 | return e 23 | } 24 | -------------------------------------------------------------------------------- /checks/certificate/publicsuffix/publicsuffix_test.go: -------------------------------------------------------------------------------- 1 | package publicsuffix 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | ) 11 | 12 | func TestPublicSuffix(t *testing.T) { 13 | cd := &certdata.Data{ 14 | Cert: &x509.Certificate{ 15 | Subject: pkix.Name{ 16 | CommonName: "*.com", 17 | }, 18 | DNSNames: []string{"*.com", "com", "*.co.uk", "gov.uk", "*.eu.com", "internalname"}, 19 | }, 20 | Type: "DV", 21 | } 22 | 23 | e := Check(cd) 24 | if len(e.List()) != 6 { 25 | for _, err := range e.List() { 26 | fmt.Println(err) 27 | } 28 | t.Errorf("Expected 6 errors, got %d", len(e.List())) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /checks/certificate/issuerdn/issuerdn.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Issuer DN Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check performs a strict verification on the extension according to the standard(s) 18 | func Check(d *certdata.Data) *errors.Errors { 19 | var e = errors.New(nil) 20 | 21 | if d.Issuer != nil && !bytes.Equal(d.Cert.RawIssuer, d.Issuer.RawSubject) { 22 | e.Err("Certificate Issuer Distinguished Name field MUST match the Subject DN of the Issuing CA") 23 | return e 24 | } 25 | 26 | return e 27 | } 28 | -------------------------------------------------------------------------------- /checks/extensions/subjectkeyid/subjectkeyid.go: -------------------------------------------------------------------------------- 1 | package subjectkeyid 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "SubjectKeyId Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 14} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("SubjectKeyId extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /testdata/notv3.pem.golden: -------------------------------------------------------------------------------- 1 | Incomplete chain for ADIF CA consejo.adifaltavelocidad.es 81228d7efe84370b394fbd52dad59884 &{[] 0 {0 0}} 2 | Processed Certificate Type: PS 3 | Certificate Errors: 9 4 | Priority: Error, Message: Certificate contains no Authority Info Access Issuers 5 | Priority: Error, Message: Certificate contains no extended key usage 6 | Priority: Error, Message: Certificate has no key usage set 7 | Priority: Error, Message: Certificate key too small: 1024 8 | Priority: Error, Message: Certificate contains no CRL or OCSP server 9 | Priority: Info, Message: emailAddress field is deprecated 10 | Priority: Info, Message: commonName field is deprecated 11 | Priority: Error, Message: Certificate doesn't contain any subjectAltName 12 | Priority: Error, Message: Certificate is not V3 (1) 13 | -------------------------------------------------------------------------------- /checks/extensions/authoritykeyid/authoritykeyid.go: -------------------------------------------------------------------------------- 1 | package authoritykeyid 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "AuthorityKeyId Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 35} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("AuthorityKeyId extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /checks/extensions/subjectaltname/subjectaltname.go: -------------------------------------------------------------------------------- 1 | package subjectaltname 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "SubjectAltName Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 17} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("SubjectAltName extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /checks/extensions/adobetimestamp/adobetimestamp.go: -------------------------------------------------------------------------------- 1 | package adobetimestamp 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "Adobe Timestamp Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 9, 1} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("Adobe Timestamp extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /checks/certificate/publickey/publickey.go: -------------------------------------------------------------------------------- 1 | package publickey 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/checks/certificate/publickey/goodkey" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "Public Key Check" 13 | 14 | func init() { 15 | checks.RegisterCertificateCheck(checkName, nil, Check) 16 | } 17 | 18 | // Check performs a strict verification on the extension according to the standard(s) 19 | func Check(d *certdata.Data) *errors.Errors { 20 | var e = errors.New(nil) 21 | 22 | gkp := goodkey.NewKeyPolicy() 23 | err := gkp.GoodKey(d.Cert.PublicKey) 24 | if err != nil { 25 | e.Err("Certificate %s", strings.ToLower(err.Error())) 26 | return e 27 | } 28 | 29 | return e 30 | } 31 | -------------------------------------------------------------------------------- /checks/extensions/pdfrevocation/pdfrevocation.go: -------------------------------------------------------------------------------- 1 | package pdfrevocation 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "PDF Certificate Revocation Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 1, 8} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("PDF Certificate Revocation extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /checks/certificate/internal/internal_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "fmt" 7 | "net" 8 | "testing" 9 | 10 | "github.com/globalsign/certlint/certdata" 11 | ) 12 | 13 | func TestInternal(t *testing.T) { 14 | cd := &certdata.Data{ 15 | Cert: &x509.Certificate{ 16 | Subject: pkix.Name{ 17 | CommonName: "localhost", 18 | }, 19 | DNSNames: []string{"localhost", "example.internal", "example.corp", "*.example.local", "*.server"}, 20 | IPAddresses: []net.IP{net.ParseIP("192.168.1.1"), net.ParseIP("172.16.1.1"), net.ParseIP("10.1.1.1")}, 21 | }, 22 | Type: "DV", 23 | } 24 | 25 | e := Check(cd) 26 | if len(e.List()) != 9 { 27 | for _, err := range e.List() { 28 | fmt.Println(err) 29 | } 30 | t.Errorf("Expected 9 errors, got %d", len(e.List())) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /checks/extensions/crldistributionpoints/crldistributionpoints.go: -------------------------------------------------------------------------------- 1 | package crldistributionpoints 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "CRLDistributionPoints Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 31} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("CRLDistributionPoints extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /checks/extensions/smimecapabilities/smimecapabilities.go: -------------------------------------------------------------------------------- 1 | package smimecapabilities 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "S/MIME Capabilities Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 15} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if ex.Critical { 25 | e.Err("S/MIME Capabilities extension set critical") 26 | } 27 | 28 | return e 29 | } 30 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestErrors(t *testing.T) { 8 | e := new(Errors) 9 | e.Warning("Warning") 10 | if e.Priority() != Warning { 11 | t.Errorf("Unexpected priority got %d, want %d", e.Priority(), Warning) 12 | } 13 | e.Err("Error") 14 | if e.Priority() != Error { 15 | t.Errorf("Unexpected priority got %d, want %d", e.Priority(), Error) 16 | } 17 | 18 | e2 := new(Errors) 19 | e.Crit("Critical") 20 | if e.Priority() != Critical { 21 | t.Errorf("Unexpected priority got %d, want %d", e.Priority(), Critical) 22 | } 23 | 24 | e.Append(e2) 25 | if e.Priority() != Critical { 26 | t.Errorf("Unexpected priority got %d, want %d", e.Priority(), Critical) 27 | } 28 | 29 | if len(e.List()) != 3 { 30 | t.Errorf("Unexpected length got %d, want %d", len(e.List()), 3) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /checks/certificate/keyusage/keyusagestring.go: -------------------------------------------------------------------------------- 1 | package keyusage 2 | 3 | import "crypto/x509" 4 | 5 | // keyUsageString returns the name of the keyusage as string 6 | func keyUsageString(ku x509.KeyUsage) string { 7 | switch ku { 8 | case x509.KeyUsageDigitalSignature: 9 | return "DigitalSignature" 10 | case x509.KeyUsageContentCommitment: 11 | return "ContentCommitment" 12 | case x509.KeyUsageKeyEncipherment: 13 | return "KeyEncipherment" 14 | case x509.KeyUsageDataEncipherment: 15 | return "DataEncipherment" 16 | case x509.KeyUsageKeyAgreement: 17 | return "KeyAgreement" 18 | case x509.KeyUsageCertSign: 19 | return "CertSign" 20 | case x509.KeyUsageCRLSign: 21 | return "CRLSign" 22 | case x509.KeyUsageEncipherOnly: 23 | return "EncipherOnly" 24 | case x509.KeyUsageDecipherOnly: 25 | return "DecipherOnly" 26 | } 27 | return "Unknown" 28 | } 29 | -------------------------------------------------------------------------------- /checks/extensions/keyusage/keyusage.go: -------------------------------------------------------------------------------- 1 | package keyusage 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "KeyUsage Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 15} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // 22 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 23 | // 24 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 25 | var e = errors.New(nil) 26 | 27 | if !ex.Critical { 28 | e.Err("KeyUsage extension SHOULD be marked as critical when present") 29 | } 30 | 31 | return e 32 | } 33 | -------------------------------------------------------------------------------- /checks/certificate/basicconstraints/basicconstraints.go: -------------------------------------------------------------------------------- 1 | package basicconstraints 2 | 3 | import ( 4 | "github.com/globalsign/certlint/certdata" 5 | "github.com/globalsign/certlint/checks" 6 | "github.com/globalsign/certlint/errors" 7 | ) 8 | 9 | const checkName = "Basic Constraints Check" 10 | 11 | func init() { 12 | checks.RegisterCertificateCheck(checkName, nil, Check) 13 | } 14 | 15 | // Check performs a strict verification on the extension according to the standard(s) 16 | func Check(d *certdata.Data) *errors.Errors { 17 | var e = errors.New(nil) 18 | 19 | switch d.Type { 20 | case "DV", "OV", "EV": 21 | if d.Cert.IsCA { 22 | e.Err("Certificate has set CA true") 23 | } 24 | if d.Cert.MaxPathLen == 0 && d.Cert.MaxPathLenZero { 25 | //e.Err("Certificate has set CA true") 26 | } 27 | if d.Cert.BasicConstraintsValid { 28 | //e.Err("Certificate has set CA true") 29 | } 30 | } 31 | 32 | return e 33 | } 34 | -------------------------------------------------------------------------------- /checks/extensions/ct/ct.go: -------------------------------------------------------------------------------- 1 | package ct 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "Certificate Transparency Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // 22 | // https://tools.ietf.org/html/rfc6962 23 | // 24 | // TODO: Check it's present in EV certificates issued after xxx 25 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 26 | var e = errors.New(nil) 27 | 28 | if ex.Critical { 29 | e.Err("Certificate Transparency extension set critical") 30 | } 31 | 32 | return e 33 | } 34 | -------------------------------------------------------------------------------- /checks/extensions/ocspnocheck/ocspnocheck.go: -------------------------------------------------------------------------------- 1 | package ocspnocheck 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "OCSP Nocheck Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 48, 1, 5} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 22 | var e = errors.New(nil) 23 | 24 | if d.Type != "OCSP" { 25 | e.Err("OCSP Nocheck extension set in non OCSP signing certificate") 26 | } 27 | 28 | if ex.Critical { 29 | e.Err("OCSP Nocheck Capabilities extension set critical") 30 | } 31 | 32 | return e 33 | } 34 | -------------------------------------------------------------------------------- /checks/extensions/authorityinfoaccess/authorityinfoaccess.go: -------------------------------------------------------------------------------- 1 | package authorityinfoaccess 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "AuthorityInfoAccess Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 1} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // TODO: Add more checks https://golang.org/src/github.com/globalsign/certlint/certdata/x509.go?s=15439:18344#L1157 22 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 23 | var e = errors.New(nil) 24 | 25 | if ex.Critical { 26 | e.Err("AuthorityInfoAccess extension set critical") 27 | } 28 | 29 | return e 30 | } 31 | -------------------------------------------------------------------------------- /certdata/certificate.go: -------------------------------------------------------------------------------- 1 | package certdata 2 | 3 | import ( 4 | "crypto/x509" 5 | "fmt" 6 | ) 7 | 8 | // Data holds the certificate and relevant information 9 | // Type can be DV, OV, EV, PS, CS, EVCS, TS, OCSP, CA 10 | type Data struct { 11 | Cert *x509.Certificate 12 | Issuer *x509.Certificate 13 | Type string 14 | } 15 | 16 | // Load raw certificate bytes into a Data struct 17 | func Load(der []byte) (*Data, error) { 18 | var err error 19 | 20 | d := new(Data) 21 | d.Cert, err = x509.ParseCertificate(der) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | if err = d.setCertificateType(); err != nil { 27 | fmt.Println(err) 28 | } 29 | 30 | return d, nil 31 | } 32 | 33 | // SetIssuer sets the issuer of a certificate 34 | // TODO: Validate if the correct issuer is given 35 | func (d *Data) SetIssuer(der []byte) error { 36 | var err error 37 | d.Issuer, err = x509.ParseCertificate(der) 38 | if err != nil { 39 | return err 40 | } 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /checks/certificate/subject/subject_test.go: -------------------------------------------------------------------------------- 1 | package subject 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | ) 11 | 12 | func TestSubject(t *testing.T) { 13 | n := &pkix.Name{ 14 | Organization: []string{"Organization Name that is exceeding the maximum length of 64 characters"}, 15 | OrganizationalUnit: []string{"Organization Unit value exceeding the maximum length of 64 characters"}, 16 | CommonName: "just-a-really-really-really-really-loooooooooong-domain-exceeding-64chars.example.com", 17 | } 18 | 19 | rdns := n.ToRDNSequence() 20 | 21 | c := &x509.Certificate{} 22 | c.Subject.FillFromRDNSequence(&rdns) 23 | 24 | cd := &certdata.Data{ 25 | Cert: c, 26 | Type: "DV", 27 | } 28 | 29 | e := Check(cd) 30 | if len(e.List()) != 6 { 31 | for _, err := range e.List() { 32 | fmt.Println(err) 33 | } 34 | t.Errorf("Expected 6 errors, got %d", len(e.List())) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /checks/certificate/internal/ipaddress.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import "net" 4 | 5 | // checkInternalIP verifies is an IP address in a registered internal range. 6 | // TODO: Check if we need to verify any IPv6 ranges 7 | // TODO: We should also check for special purpose IP ranges, we might want to do that in a specific function 8 | func checkInternalIP(ip net.IP) bool { 9 | var privIPSpace []net.IPNet 10 | // 10.0.0.0/8 11 | privIPSpace = append(privIPSpace, net.IPNet{ 12 | IP: net.IP{0xa, 0x0, 0x0, 0x0}, 13 | Mask: net.IPMask{0xff, 0x0, 0x0, 0x0}, 14 | }) 15 | // 172.16.0.0/12" 16 | privIPSpace = append(privIPSpace, net.IPNet{ 17 | IP: net.IP{0xac, 0x10, 0x0, 0x0}, 18 | Mask: net.IPMask{0xff, 0xf0, 0x0, 0x0}, 19 | }) 20 | // 192.168.0.0/16 21 | privIPSpace = append(privIPSpace, net.IPNet{ 22 | IP: net.IP{0xc0, 0xa8, 0x0, 0x0}, 23 | Mask: net.IPMask{0xff, 0xff, 0x0, 0x0}, 24 | }) 25 | 26 | for _, ipSpace := range privIPSpace { 27 | if ipSpace.Contains(ip) { 28 | return true 29 | } 30 | } 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /checks/certificate.go: -------------------------------------------------------------------------------- 1 | package checks 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/errors" 8 | ) 9 | 10 | var certMutex = &sync.Mutex{} 11 | 12 | type certificate []certificateCheck 13 | 14 | type certificateCheck struct { 15 | name string 16 | filter *Filter 17 | f func(*certdata.Data) *errors.Errors 18 | } 19 | 20 | // Certificate contains all imported certificate checks 21 | var Certificate certificate 22 | 23 | // RegisterCertificateCheck adds a new check to Cerificates 24 | func RegisterCertificateCheck(name string, filter *Filter, f func(*certdata.Data) *errors.Errors) { 25 | certMutex.Lock() 26 | Certificate = append(Certificate, certificateCheck{name, filter, f}) 27 | certMutex.Unlock() 28 | } 29 | 30 | // Check runs all the registered certificate checks 31 | func (c certificate) Check(d *certdata.Data) *errors.Errors { 32 | var e = errors.New(nil) 33 | 34 | for _, cc := range c { 35 | if cc.filter != nil && !cc.filter.Check(d) { 36 | continue 37 | } 38 | e.Append(cc.f(d)) 39 | } 40 | 41 | return e 42 | } 43 | -------------------------------------------------------------------------------- /checks/extensions/nameconstraints/nameconstraints.go: -------------------------------------------------------------------------------- 1 | package nameconstraints 2 | 3 | import ( 4 | "encoding/asn1" 5 | 6 | "crypto/x509/pkix" 7 | 8 | "github.com/globalsign/certlint/certdata" 9 | "github.com/globalsign/certlint/checks" 10 | "github.com/globalsign/certlint/errors" 11 | ) 12 | 13 | const checkName = "NameConstraints Extension Check" 14 | 15 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 30} 16 | 17 | func init() { 18 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 19 | } 20 | 21 | // Check performs a strict verification on the extension according to the standard(s) 22 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 23 | var e = errors.New(nil) 24 | 25 | // NameConstraints do officially need to be set critical, often they are not 26 | // because many implementations still don't support Name Constraints. 27 | if !ex.Critical { 28 | e.Warning("NameConstraints extension set non-critical") 29 | } 30 | 31 | // NameConstraints should only be included in CA or subordinate certificates 32 | if !d.Cert.IsCA { 33 | e.Err("End entity certificate should not contain a NameConstraints extension") 34 | } 35 | 36 | return e 37 | } 38 | -------------------------------------------------------------------------------- /testdata/notv3.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDCjCCAfICEQCBIo1+/oQ3CzlPvVLa1ZiEMA0GCSqGSIb3DQEBBQUAMC4xCzAJ 3 | BgNVBAYTAkVTMQ0wCwYDVQQKEwRBRElGMRAwDgYDVQQDEwdBRElGIENBMB4XDTE0 4 | MDEwOTA4MjEyMloXDTE0MTAzMTE0NDQyM1owgdsxDzANBgNVBAcTBk1hZHJpZDE+ 5 | MDwGA1UECxM1RGlyZWNjaW9uIGRlIFNpc3RlbWFzIHkgVGVjbm9sb2dpYXMgZGUg 6 | bGEgSW5mb3JtYWNpb24xGzAZBgkqhkiG9w0BCQEWDGRyeWNAYWRpZi5lczELMAkG 7 | A1UEBhMCRVMxNzA1BgNVBAoTLkFkbWluaXN0cmFkb3IgZGUgSW5mcmFlc3RydWN0 8 | dXJhcyBGZXJyb3ZpYXJpYXMxJTAjBgNVBAMTHGNvbnNlam8uYWRpZmFsdGF2ZWxv 9 | Y2lkYWQuZXMwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMz3zfPddElxsOJd 10 | PFONf7tPNkqHeuypm4ZcS5B5HAkbp2jIuLyoKKnbDu0yaCl/SDAhYviLPyCqXxX6 11 | lPLwg0WL659tZJ4r80x78dfW95bEzWYaDSyVzmxPrOK1QkXPstisPApqi/SRdWI4 12 | imKltYe+jm+hfjLurgaXv8Oao2DlAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAJuI 13 | giIohT2M2rZ/KJbPHa/+h68x5UIPlV1hVtfx3xI9dDcy5cLNRH6ba6ARvIadl6NZ 14 | VSSbMQlnwaTHoEkXmuRhc8ELsgh7PQpprNsdJ32dydYIGxqdpblHHzBjV+ZLX2O2 15 | RpiLEncfXyGHWQOLSQiCvoNJn5XeAvz6NkuVRVsi9Vz5tcNlMmJNgBhUk2nj0nQb 16 | t2VCdgZ928oJ0hY3kQ9AH+cZkk+bGbtF6D1Is9U6si01KhSHUT+NbZYEV4ti3br2 17 | 9meKEmtTJXhuUk0GuunlgaOgUDUqkpx0dzEvhlRU315jhq4+nLKZ/l47yJ11wnBu 18 | 8PyCwDzIDCSU++aLKVc= 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /asn1/asn1.go: -------------------------------------------------------------------------------- 1 | package asn1 2 | 3 | import ( 4 | "encoding/asn1" 5 | 6 | "github.com/globalsign/certlint/errors" 7 | ) 8 | 9 | type Linter struct { 10 | e errors.Errors 11 | } 12 | 13 | // CheckStruct returns a list of errors based on strict checks on the raw ASN1 14 | // encoding of the input der. 15 | func (l *Linter) CheckStruct(der []byte) *errors.Errors { 16 | l.walk(der) 17 | if l.e.IsError() { 18 | return &l.e 19 | } 20 | return nil 21 | } 22 | 23 | // walk is a recursive call that walks over the ASN1 structured data until no 24 | // remaining bytes are left. For each non compound it will call the ASN1 format 25 | // checker. 26 | func (l *Linter) walk(der []byte) { 27 | var err error 28 | var d asn1.RawValue 29 | 30 | for len(der) > 0 { 31 | der, err = asn1.Unmarshal(der, &d) 32 | if err != nil { 33 | // Errors should be included in the report, but allow format checking when 34 | // data has been decoded. 35 | l.e.Err(err.Error()) 36 | if len(d.Bytes) == 0 { 37 | return 38 | } 39 | } 40 | 41 | // A compound is an ASN.1 container that contains other structs. 42 | if d.IsCompound { 43 | l.walk(d.Bytes) 44 | } else { 45 | l.CheckFormat(d) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /checks/extensions/all/all.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | // Import all default extensions 5 | _ "github.com/globalsign/certlint/checks/extensions/adobetimestamp" 6 | _ "github.com/globalsign/certlint/checks/extensions/authorityinfoaccess" 7 | _ "github.com/globalsign/certlint/checks/extensions/authoritykeyid" 8 | _ "github.com/globalsign/certlint/checks/extensions/basicconstraints" 9 | _ "github.com/globalsign/certlint/checks/extensions/crldistributionpoints" 10 | _ "github.com/globalsign/certlint/checks/extensions/ct" 11 | _ "github.com/globalsign/certlint/checks/extensions/extkeyusage" 12 | _ "github.com/globalsign/certlint/checks/extensions/keyusage" 13 | _ "github.com/globalsign/certlint/checks/extensions/nameconstraints" 14 | _ "github.com/globalsign/certlint/checks/extensions/ocspmuststaple" 15 | _ "github.com/globalsign/certlint/checks/extensions/ocspnocheck" 16 | _ "github.com/globalsign/certlint/checks/extensions/pdfrevocation" 17 | _ "github.com/globalsign/certlint/checks/extensions/policyidentifiers" 18 | _ "github.com/globalsign/certlint/checks/extensions/smimecapabilities" 19 | _ "github.com/globalsign/certlint/checks/extensions/subjectaltname" 20 | _ "github.com/globalsign/certlint/checks/extensions/subjectkeyid" 21 | ) 22 | -------------------------------------------------------------------------------- /checks/certificate/all/all.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | // Import all default checks 5 | _ "github.com/globalsign/certlint/checks/certificate/aiaissuers" 6 | _ "github.com/globalsign/certlint/checks/certificate/basicconstraints" 7 | _ "github.com/globalsign/certlint/checks/certificate/extensions" 8 | _ "github.com/globalsign/certlint/checks/certificate/extkeyusage" 9 | _ "github.com/globalsign/certlint/checks/certificate/internal" 10 | _ "github.com/globalsign/certlint/checks/certificate/issuerdn" 11 | _ "github.com/globalsign/certlint/checks/certificate/keyusage" 12 | _ "github.com/globalsign/certlint/checks/certificate/publickey" 13 | _ "github.com/globalsign/certlint/checks/certificate/publicsuffix" 14 | _ "github.com/globalsign/certlint/checks/certificate/revocation" 15 | _ "github.com/globalsign/certlint/checks/certificate/serialnumber" 16 | _ "github.com/globalsign/certlint/checks/certificate/signaturealgorithm" 17 | _ "github.com/globalsign/certlint/checks/certificate/subject" 18 | _ "github.com/globalsign/certlint/checks/certificate/subjectaltname" 19 | _ "github.com/globalsign/certlint/checks/certificate/validity" 20 | _ "github.com/globalsign/certlint/checks/certificate/version" 21 | _ "github.com/globalsign/certlint/checks/certificate/wildcard" 22 | ) 23 | -------------------------------------------------------------------------------- /checks/certificate/wildcard/wildcard.go: -------------------------------------------------------------------------------- 1 | package wildcard 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Wildcard(s) Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check performs a strict verification on the extension according to the standard(s) 18 | func Check(d *certdata.Data) *errors.Errors { 19 | var e = errors.New(nil) 20 | 21 | switch d.Type { 22 | case "EV": 23 | if strings.LastIndex(d.Cert.Subject.CommonName, "*") > -1 { 24 | e.Err("Certificate should not contain a wildcard") 25 | } 26 | for _, n := range d.Cert.DNSNames { 27 | if strings.LastIndex(n, "*") > -1 { 28 | e.Err("Certificate subjectAltName '%s' should not contain a wildcard", n) 29 | } 30 | } 31 | case "DV", "OV": 32 | if strings.LastIndex(d.Cert.Subject.CommonName, "*") > 0 { 33 | e.Err("Certificate wildcard is only allowed as prefix") 34 | } 35 | for _, n := range d.Cert.DNSNames { 36 | if strings.LastIndex(n, "*") > 0 { 37 | e.Err("Certificate subjectAltName '%s' wildcard is only allowed as prefix", n) 38 | } 39 | } 40 | } 41 | 42 | return e 43 | } 44 | -------------------------------------------------------------------------------- /testdata/notv3_2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDcDCCAlgCEQCmF6DCgoK4qzOYy/s5LGpHMA0GCSqGSIb3DQEBBQUAMG8xCzAJBgNVBAYTAkdC 3 | MRgwFgYDVQQKEw9UcnVzdGlzIExpbWl0ZWQxFDASBgNVBAsTC1RydXN0aXMgRlBTMTAwLgYDVQQD 4 | EydUcnVzdGlzIEhlYWx0aGNhcmUgVFQgSXNzdWluZyBBdXRob3JpdHkwHhcNMTQwNTE5MTYwNjMx 5 | WhcNMTcwNTE5MTYwNjMxWjB9MQswCQYDVQQGEwJHQjEPMA0GA1UECAwGTG9uZG9uMQ8wDQYDVQQH 6 | DAZMb25kb24xHzAdBgNVBAoMFkJhcnRzIEhlYWx0aCBOSFMgVHJ1c3QxDDAKBgNVBAsMA0lDVDEd 7 | MBsGA1UEAwwUKi5iYXJ0c2hlYWx0aC5uaHMudWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 8 | AoIBAQDuHnxNkUoEPmoelwLHc8sFX1Ck9hOKShVKaj2px3fnwEvcsOj1utvGIOXzyvlVhYjAZBOE 9 | 0dw9ahT8ZxPGgS0E+54p5IEuWepafUL26oDiRvgohJvslnz95pFV7VhYfcS9yjCQmnRv4/P7H234 10 | BgtheFJyroOCBSglVO+KH11xLsiFE/0tpCuBXUeKRQ3ANyG6ZD6ayM3SgWbKZjnsHqum0+qdTgJR 11 | W/2IVrS3avn7Z9BZPVfphs3UyfF9vZphRdvA1o6MdxpXaqscHLChHR12b449jIlMJ5C7kwneOvM+ 12 | iAQTj8qzaIFg+lO45Jq0hqb5PtYMbcFH2BplZOKV95DHAgMBAAEwDQYJKoZIhvcNAQEFBQADggEB 13 | AAF4r8yRnRmHinUvgfQQ2M0kUVV1xJIwgPEpM3ByvaVwhCOaIAFzyX7e1d9epY9w5BahXBGHHdaj 14 | dAOGtISYMg4MHYd2pq2tTbp8u+b42ujYPdBH2x9vMrKCsZu1T3e2ik3wyvUFCoGj2VVzRL/ckcvG 15 | Y0lttCxddBBen1OrFyq1BCj02hMDIysYBv90zkkGpHivcbqWvMt/PN92trYZt3Ej+Vx7JIC769Ia 16 | 7RAGn3XVncG+CjZq8n/HWZftc2nfsvteScDa10zrFW9dMiFogQ6D/8o7PmW5psxL79cr9UlkSDjA 17 | CF/o7BWcZGuDFX3NWH0caAunVSzQJZBKmQxbR5Q= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /checks/filter.go: -------------------------------------------------------------------------------- 1 | package checks 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | ) 8 | 9 | // Filter defines condition on when a check is performed or not 10 | type Filter struct { 11 | Type []string 12 | IssuedBefore *time.Time 13 | IssuedAfter *time.Time 14 | ExpiresBefore *time.Time 15 | ExpiresAfter *time.Time 16 | } 17 | 18 | // Check returns true if a certificate complies with the given filter 19 | func (f *Filter) Check(d *certdata.Data) bool { 20 | // Is certificate recognised as one of the given types 21 | if len(f.Type) > 0 { 22 | var inFilter bool 23 | for _, t := range f.Type { 24 | if d.Type == t { 25 | inFilter = true 26 | } 27 | } 28 | if !inFilter { 29 | return false 30 | } 31 | } 32 | 33 | // Issued before given date 34 | if f.IssuedBefore != nil && !d.Cert.NotBefore.Before(*f.IssuedBefore) { 35 | return false 36 | } 37 | // Issued after given date 38 | if f.IssuedAfter != nil && !d.Cert.NotBefore.After(*f.IssuedAfter) { 39 | return false 40 | } 41 | // Expires before given date 42 | if f.ExpiresBefore != nil && !d.Cert.NotBefore.Before(*f.ExpiresBefore) { 43 | return false 44 | } 45 | // Expires after given date 46 | if f.ExpiresAfter != nil && !d.Cert.NotAfter.After(*f.ExpiresAfter) { 47 | return false 48 | } 49 | 50 | return true 51 | } 52 | -------------------------------------------------------------------------------- /checks/extensions/policyidentifiers/policyidentifiers.go: -------------------------------------------------------------------------------- 1 | package policyidentifiers 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "PolicyIdentifiers Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 32} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // 22 | // Section 7.1.2.3 (a) of the Baseline Requirements states: 23 | // 24 | // This extension MUST be present and SHOULD NOT be marked critical. 25 | // 26 | // A Policy Identifier, defined by the issuing CA, that indicates a 27 | // Certificate Policy asserting the issuing CA's adherence to and compliance 28 | // with these Requirements. 29 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 30 | var e = errors.New(nil) 31 | 32 | // certificatePolicies SHOULD NOT be marked critical 33 | if ex.Critical { 34 | e.Err("PolicyIdentifiers extension set critical") 35 | } 36 | 37 | // A policy must be defined 38 | if len(d.Cert.PolicyIdentifiers) == 0 { 39 | e.Err("PolicyIdentifiers not present") 40 | } 41 | 42 | return e 43 | } 44 | -------------------------------------------------------------------------------- /checks/certificate/signaturealgorithm/signaturealgorithm.go: -------------------------------------------------------------------------------- 1 | package signaturealgorithm 2 | 3 | import ( 4 | "crypto/x509" 5 | "time" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "Signature Algorithm Check" 13 | 14 | func init() { 15 | filter := &checks.Filter{ 16 | Type: []string{"DV", "OV", "IV", "EV"}, 17 | } 18 | checks.RegisterCertificateCheck(checkName, filter, Check) 19 | } 20 | 21 | // Check performs a strict verification on the extension according to the standard(s) 22 | func Check(d *certdata.Data) *errors.Errors { 23 | var e = errors.New(nil) 24 | 25 | // Check if we use SHA1 or less (MD5, MD2) 26 | if d.Cert.SignatureAlgorithm > x509.SHA1WithRSA && 27 | d.Cert.SignatureAlgorithm != x509.DSAWithSHA1 && 28 | d.Cert.SignatureAlgorithm != x509.ECDSAWithSHA1 { 29 | return e 30 | } 31 | 32 | if d.Cert.NotBefore.After(time.Date(2016, 1, 1, 0, 0, 0, 0, time.UTC)) { 33 | e.Err("Certificate is using SHA1, but is issued on/after 1 Jan 2016") 34 | return e 35 | } 36 | 37 | if d.Cert.NotBefore.After(time.Date(2015, 1, 16, 0, 0, 0, 0, time.UTC)) && 38 | d.Cert.NotAfter.After(time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC)) { 39 | e.Err("Certificate is using SHA1, but is still valid on/after 1 Jan 2017") 40 | return e 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /checks/certificate/internal/internal.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/globalsign/certlint/certdata" 5 | "github.com/globalsign/certlint/checks" 6 | "github.com/globalsign/certlint/errors" 7 | ) 8 | 9 | const checkName = "Internal Names and IP addresses Check" 10 | 11 | func init() { 12 | filter := &checks.Filter{ 13 | Type: []string{"DV", "OV", "IV", "EV"}, 14 | } 15 | checks.RegisterCertificateCheck(checkName, filter, Check) 16 | } 17 | 18 | // Check performs a strict verification on the extension according to the standard(s) 19 | // TODO: Add more checks https://golang.org/src/crypto/x509/x509.go?s=15439:18344#L1157 20 | func Check(d *certdata.Data) *errors.Errors { 21 | var e = errors.New(nil) 22 | 23 | if checkInternalName(d.Cert.Subject.CommonName) { 24 | e.Err("Certificate contains an internal server name in the common name '%s'", d.Cert.Subject.CommonName) 25 | } 26 | for _, n := range d.Cert.DNSNames { 27 | if checkInternalName(n) { 28 | e.Err("Certificate subjectAltName '%s' contains an internal server name", n) 29 | } 30 | } 31 | 32 | // Check for internal IP addresses 33 | for _, ip := range d.Cert.IPAddresses { 34 | if !ip.IsGlobalUnicast() { 35 | e.Err("Certificate subjectAltName '%v' contains a non global unicast IP address", ip) 36 | } 37 | if checkInternalIP(ip) { 38 | e.Err("Certificate subjectAltName '%v' contains a private or local IP address", ip) 39 | } 40 | } 41 | 42 | return e 43 | } 44 | -------------------------------------------------------------------------------- /checks/extensions/extkeyusage/extkeyusage.go: -------------------------------------------------------------------------------- 1 | package extkeyusage 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "encoding/asn1" 7 | 8 | "github.com/globalsign/certlint/certdata" 9 | "github.com/globalsign/certlint/checks" 10 | "github.com/globalsign/certlint/errors" 11 | ) 12 | 13 | const checkName = "ExtKeyUsage Extension Check" 14 | 15 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 37} 16 | 17 | func init() { 18 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 19 | } 20 | 21 | // Check performs a strict verification on the extension according to the standard(s) 22 | // 23 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 24 | // 25 | // This extension MAY, at the option of the certificate issuer, be either critical or non-critical. 26 | // 27 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 28 | var e = errors.New(nil) 29 | 30 | // RFC: In general, this extension will appear only in end entity certificates. 31 | if d.Cert.IsCA { 32 | e.Err("In general ExtKeyUsage will appear only in end entity certificates") 33 | } 34 | 35 | // RFC: Conforming CAs SHOULD NOT mark this extension as critical if the 36 | // anyExtendedKeyUsage KeyPurposeId is present. 37 | if ex.Critical { 38 | for _, ku := range d.Cert.ExtKeyUsage { 39 | if ku == x509.ExtKeyUsageAny { 40 | e.Err("ExtKeyUsage extension SHOULD NOT be critical if anyExtendedKeyUsage is present") 41 | break 42 | } 43 | } 44 | } 45 | 46 | return e 47 | } 48 | -------------------------------------------------------------------------------- /checks/certificate/serialnumber/serialnumber.go: -------------------------------------------------------------------------------- 1 | package serialnumber 2 | 3 | import ( 4 | "math/big" 5 | "time" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "Certificate Serial Number Check" 13 | 14 | func init() { 15 | checks.RegisterCertificateCheck(checkName, nil, Check) 16 | } 17 | 18 | // Check performs a strict verification on the extension according to the standard(s) 19 | func Check(d *certdata.Data) *errors.Errors { 20 | var e = errors.New(nil) 21 | 22 | if d.Cert.SerialNumber.Cmp(big.NewInt(0)) == -1 { 23 | e.Err("Certificate serial number MUST be a positive integer (%d)", d.Cert.SerialNumber) 24 | } 25 | 26 | // Remaining checks are not relevant for CA certificates 27 | if d.Cert.IsCA { 28 | return e 29 | } 30 | 31 | // https://cabforum.org/2016/07/08/ballot-164/ 32 | if d.Cert.NotBefore.After(time.Date(2016, 9, 30, 0, 0, 0, 0, time.UTC)) { 33 | if d.Cert.SerialNumber.BitLen() < 64 { 34 | e.Err("Certificate serial number should be 64 bits but contains %d bits", d.Cert.SerialNumber.BitLen()) 35 | } 36 | } else { 37 | // all new end-entity certificates must contain at least 20 bits of unpredictable random data (preferably in the serial number). 38 | if d.Cert.SerialNumber.BitLen() < 20 { 39 | e.Warning("Certificate serial number must contain at least 20 bits of unpredictable random data, found only %d bits", d.Cert.SerialNumber.BitLen()) 40 | } 41 | } 42 | 43 | return nil 44 | } 45 | -------------------------------------------------------------------------------- /checks/certificate/subjectaltname/subjectaltname_test.go: -------------------------------------------------------------------------------- 1 | package subjectaltname 2 | 3 | import ( 4 | "crypto/x509" 5 | "crypto/x509/pkix" 6 | "fmt" 7 | "testing" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | ) 11 | 12 | func TestSubjectAltNameDNSNames(t *testing.T) { 13 | cd := &certdata.Data{ 14 | Cert: &x509.Certificate{ 15 | Subject: pkix.Name{ 16 | CommonName: "www.example .com", 17 | }, 18 | DNSNames: []string{"www..example.com", "www .example.com", 19 | "www,example.com", "*.example.com", "www.example.com", "w_w.example.com", 20 | "グローバルサイン.com", "اختبارنطاق.شبكة", "-www.example.com", 21 | "www.ex_mple.com", "homoglyph.ехаmрlе.ϲоm"}, 22 | }, 23 | Type: "DV", 24 | } 25 | 26 | e := Check(cd) 27 | if len(e.List()) != 9 { 28 | t.Errorf("Expected 9 errors, got %d", len(e.List())) 29 | } 30 | for _, err := range e.List() { 31 | fmt.Println(err) 32 | } 33 | } 34 | 35 | // TODO: Set EmailAddresses in the Subject DN 36 | func TestSubjectAltNameEmailAddresses(t *testing.T) { 37 | cd := &certdata.Data{ 38 | Cert: &x509.Certificate{ 39 | EmailAddresses: []string{"john.doe@example..com", "john.doe@example .com", 40 | "john.doe@example,com", "john.doe@example.com", 41 | "john.doe@グローバルサイン.com", "john.doe@اختبارنطاق.شبكة", 42 | "john.doe@-example.com", "john.doe@ex_mple.com", "homoglyph@ехаmрlе.ϲоm"}, 43 | }, 44 | Type: "PS", 45 | } 46 | 47 | e := Check(cd) 48 | if len(e.List()) != 6 { 49 | t.Errorf("Expected 6 errors, got %d", len(e.List())) 50 | } 51 | for _, err := range e.List() { 52 | fmt.Println(err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /checks/certificate/publicsuffix/publicsuffix.go: -------------------------------------------------------------------------------- 1 | package publicsuffix 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | 11 | psl "golang.org/x/net/publicsuffix" 12 | ) 13 | 14 | const checkName = "Public Suffix (xTLD) Check" 15 | 16 | func init() { 17 | filter := &checks.Filter{ 18 | Type: []string{"DV", "OV", "IV", "EV"}, 19 | } 20 | checks.RegisterCertificateCheck(checkName, filter, Check) 21 | } 22 | 23 | // Check performs a strict verification on the extension according to the standard(s) 24 | func Check(d *certdata.Data) *errors.Errors { 25 | var e = errors.New(nil) 26 | 27 | if len(d.Cert.Subject.CommonName) > 0 { 28 | suffix, icann := psl.PublicSuffix(strings.ToLower(d.Cert.Subject.CommonName)) 29 | if fmt.Sprintf("*.%s", suffix) == d.Cert.Subject.CommonName || suffix == d.Cert.Subject.CommonName { 30 | // if there is a dot on the suffix, it must be on the psl 31 | if icann || strings.Count(suffix, ".") > 0 { 32 | e.Err("Certificate CommonName %q equals %q from the public suffix list", d.Cert.Subject.CommonName, suffix) 33 | } 34 | } 35 | } 36 | 37 | for _, n := range d.Cert.DNSNames { 38 | suffix, icann := psl.PublicSuffix(strings.ToLower(n)) 39 | if fmt.Sprintf("*.%s", suffix) == n || suffix == n { 40 | // if there is a dot on the suffix, it must be on the psl 41 | if icann || strings.Count(suffix, ".") > 0 { 42 | e.Err("Certificate subjectAltName %q equals %q from the public suffix list", n, suffix) 43 | } 44 | } 45 | } 46 | 47 | return e 48 | } 49 | -------------------------------------------------------------------------------- /testdata/gtenonsan.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIETDCCAzSgAwIBAgIOAgAAAAABRWvhpQ5miD4wDQYJKoZIhvcNAQEFBQAwRjEX 3 | MBUGA1UEChMOQ3liZXJ0cnVzdCBJbmMxKzApBgNVBAMTIkN5YmVydHJ1c3QgUHVi 4 | bGljIFN1cmVTZXJ2ZXIgU1YgQ0EwHhcNMTQwNDE2MTg1MjA0WhcNMTUwNDE2MTg1 5 | MjA0WjCBqzELMAkGA1UEBhMCVVMxHTAbBgNVBAgTFERJU1RSSUNUIE9GIENPTFVN 6 | QklBMRMwEQYDVQQHEwpXYXNoaW5ndG9uMSowKAYDVQQKEyFFeGVjdXRpdmUgT2Zm 7 | aWNlIG9mIHRoZSBQcmVzaWRlbnQxITAfBgNVBAsTGE9mZmljZSBvZiBBZG1pbmlz 8 | dHJhdGlvbjEZMBcGA1UEAxQQKi53aGl0ZWhvdXNlLmdvdjCCASIwDQYJKoZIhvcN 9 | AQEBBQADggEPADCCAQoCggEBAO2uEhel367IxR7dkBmGO13flKieJON4x3XJDoLT 10 | oY4Zxc2FoI752tzgGqgatrt62/HKCNJpUhHaW6E82hBSOYeRZFlj5gSm1SGqFjfq 11 | jJmn7w2ZL2jCYZ37gku5D1U5wZoDcR8XZrY8DyiK/f6CTD21WJXw9qMIMfkcFmO0 12 | nlbMzL9JT1wCenkcSuA3BmtdIVF5wgvfBMDJ5X19bTJF3I5CsCcIW27YMawegmvr 13 | zwHxlSmPR8YAx5hfJF8GUb4ixTHJscrEGJ5NVtxX3TgBj8PC7u8ODylirtZsPKfe 14 | WBRbXQes9LTJaU/dHSNh/aV8ZTGvxyv6aZ4hwDACVZ/owqkCAwEAAaOB0TCBzjAf 15 | BgNVHSMEGDAWgBQEmGDfgBuWSV1lVi2lLAkkCuzcuTA/BgNVHR8EODA2MDSgMqAw 16 | hi5odHRwOi8vY3JsLm9tbmlyb290LmNvbS9QdWJsaWNTdXJlU2VydmVyU1YuY3Js 17 | MB0GA1UdDgQWBBSOucZBXuT1jfyxBBKdYmAeceIt1zAJBgNVHRMEAjAAMA4GA1Ud 18 | DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwEQYJYIZI 19 | AYb4QgEBBAQDAgbAMA0GCSqGSIb3DQEBBQUAA4IBAQB24a8m5thhwhJGY2dFhBpt 20 | QcUWXzO9FGqw6mfz2cej/nngwjRU/QHc7nL0wIcp+MvB8TB7nt6GlGOabZr+h9NP 21 | uWKin/gvUk6jARpJIALHvnAnYKx7heXdsGLDOEhyPvFqnM2NrI729M/ybrUcuEpr 22 | HqfBn2sMvg+Y0WHghptpTEWb6cXw1PQFkk5ujMp3E34wCBDYfnAXm0LXLLGArUJp 23 | R/d3Jc5Owuah0nW0geA+yH0IKZ8saikghXecS6Glf5Nbd5yY/HJfxlxOoh8GFSOG 24 | idYDg38nSaIqUjAIbxhJCgtdKB4evYPz9b7Gy7wkZT9Dki4NI0swVVNi/LrJyl9b 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /checks/certificate/validity/validity.go: -------------------------------------------------------------------------------- 1 | package validity 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Validity Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check performs a strict verification on the extension according to the standard(s) 18 | func Check(d *certdata.Data) *errors.Errors { 19 | var e = errors.New(nil) 20 | 21 | switch d.Type { 22 | case "EV": 23 | if d.Cert.NotBefore.After(time.Date(2017, 3, 17, 0, 0, 0, 0, time.UTC)) { 24 | if d.Cert.NotAfter.After(d.Cert.NotBefore.AddDate(0, 0, 825)) { 25 | e.Err("EV Certificate LifeTime exceeds 825 days") 26 | return e 27 | } 28 | } else { 29 | if d.Cert.NotAfter.After(d.Cert.NotBefore.AddDate(0, 27, 0)) { 30 | e.Err("EV Certificate LifeTime exceeds 27 months") 31 | return e 32 | } 33 | } 34 | case "DV", "OV": 35 | if d.Cert.NotBefore.After(time.Date(2018, 3, 1, 0, 0, 0, 0, time.UTC)) { 36 | if d.Cert.NotAfter.After(d.Cert.NotBefore.AddDate(0, 0, 825)) { 37 | e.Err("Certificate LifeTime exceeds 825 days") 38 | return e 39 | } 40 | } else if d.Cert.NotBefore.After(time.Date(2016, 7, 1, 0, 0, 0, 0, time.UTC)) { 41 | if d.Cert.NotAfter.After(d.Cert.NotBefore.AddDate(0, 39, 0)) { 42 | e.Err("Certificate LifeTime exceeds 39 months") 43 | return e 44 | } 45 | } else { 46 | if d.Cert.NotAfter.After(d.Cert.NotBefore.AddDate(0, 60, 0)) { 47 | e.Err("Certificate LifeTime exceeds 60 months") 48 | return e 49 | } 50 | } 51 | } 52 | return e 53 | } 54 | -------------------------------------------------------------------------------- /checks/extensions.go: -------------------------------------------------------------------------------- 1 | package checks 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | "github.com/globalsign/certlint/errors" 11 | ) 12 | 13 | var extMutex = &sync.Mutex{} 14 | 15 | type extensions []extensionCheck 16 | 17 | type extensionCheck struct { 18 | name string 19 | oid asn1.ObjectIdentifier 20 | filter *Filter 21 | f func(pkix.Extension, *certdata.Data) *errors.Errors 22 | } 23 | 24 | // Extensions contains all imported extension checks 25 | var Extensions extensions 26 | 27 | // RegisterExtensionCheck adds a new check to Extensions 28 | func RegisterExtensionCheck(name string, oid asn1.ObjectIdentifier, filter *Filter, f func(pkix.Extension, *certdata.Data) *errors.Errors) { 29 | extMutex.Lock() 30 | Extensions = append(Extensions, extensionCheck{name, oid, filter, f}) 31 | extMutex.Unlock() 32 | } 33 | 34 | // Check lookups the registered extension checks and runs all checks with the 35 | // same Object Identifier. 36 | func (ex extensions) Check(ext pkix.Extension, d *certdata.Data) *errors.Errors { 37 | var e = errors.New(nil) 38 | var found bool 39 | 40 | for _, ec := range ex { 41 | if ec.oid.Equal(ext.Id) { 42 | found = true 43 | if ec.filter != nil && ec.filter.Check(d) { 44 | continue 45 | } 46 | e.Append(ec.f(ext, d)) 47 | } 48 | } 49 | 50 | if !found { 51 | // Don't report private enterprise extensions as unknown, registered private 52 | // extensions have still been checked above. 53 | if !strings.HasPrefix(ext.Id.String(), "1.3.6.1.4.1.") { 54 | e.Warning("Certificate contains unknown extension (%s)", ext.Id.String()) 55 | } 56 | } 57 | 58 | return e 59 | } 60 | -------------------------------------------------------------------------------- /testdata/512bit.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEsTCCA5mgAwIBAgIQBGfdiMVddmRAgTIJ8k5IpzANBgkqhkiG9w0BAQUFADBl 3 | MQswCQYDVQQGEwJjaDERMA8GA1UEChMIU3dpc3Njb20xJTAjBgNVBAsTHERpZ2l0 4 | YWwgQ2VydGlmaWNhdGUgU2VydmljZXMxHDAaBgNVBAMTE1N3aXNzY29tIFJ1Ymlu 5 | IENBIDEwHhcNMTIwNTI1MTAxMDI3WhcNMTUwNTI1MTAxMDI3WjCBhDELMAkGA1UE 6 | BhMCQ0gxDTALBgNVBAgTBEJlcm4xDTALBgNVBAcTBEJlcm4xFDASBgNVBAoTC1NC 7 | Qi9DRkYvRkZTMQswCQYDVQQLEwJJVDEUMBIGA1UEAxMLYXZpcy5zYmIuY2gxHjAc 8 | BgkqhkiG9w0BCQEWD3NzbGFkbWluQHNiYi5jaDBcMA0GCSqGSIb3DQEBAQUAA0sA 9 | MEgCQQDhcUq+5xy4fJaYxdpliglPCU6HsgOYVlzfB0Xkw2bcZ+RckatB/GKebhoC 10 | 6UAFCXUSx0tnym7kq1Rfn/VyScgjAgMBAAGjggIDMIIB/zAfBgNVHSMEGDAWgBQt 11 | wqejYz4/g0erSDM2gYX31OmswDB2BggrBgEFBQcBAQRqMGgwLgYIKwYBBQUHMAGG 12 | Imh0dHA6Ly9vY3NwLnN3aXNzZGlnaWNlcnQuY2gvcnViaW4wNgYIKwYBBQUHMAKG 13 | Kmh0dHA6Ly9haWEuc3dpc3NkaWdpY2VydC5jaC9zZGNzLXJ1YmluLmNydDBIBgNV 14 | HSAEQTA/MD0GBmCFdAFTBDAzMDEGCCsGAQUFBwIBFiVodHRwOi8vd3d3LnN3aXNz 15 | ZGlnaWNlcnQuY2gvZG9jdW1lbnRzMIG5BgNVHR8EgbEwga4wMKAuoCyGKmh0dHA6 16 | Ly9jcmwuc3dpc3NkaWdpY2VydC5jaC9zZGNzLXJ1YmluLmNybDB6oHigdoZ0bGRh 17 | cDovL2xkYXAuc3dpc3NkaWdpY2VydC5jaC9DTj1Td2lzc2NvbSUyMFJ1YmluJTIw 18 | Q0ElMjAxLGRjPXJ1YmluLGRjPXN3aXNzZGlnaWNlcnQsZGM9Y2g/Y2VydGlmaWNh 19 | dGVSZXZvY2F0aW9uTGlzdD8wDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsG 20 | AQUFBwMBMBoGA1UdEQQTMBGBD3NzbGFkbWluQHNiYi5jaDAdBgNVHQ4EFgQU8cjw 21 | QuX/vXPjTiu1dtRw4jTCZ/UwDQYJKoZIhvcNAQEFBQADggEBAA9tZq23y5Ws7bMA 22 | Zy5zNgCpwXEi+kIal+tJvTxW1upoSGz21EHKhMiCuU+ukpBy5QbV2r4fMj8NyRqt 23 | IdRA+G6EbDns1PAj1FHbfjvjP6tf6N451QZhcr0auJgitLjjPLDghutWsjuQPV2k 24 | 2M6yRVXzi99Pf7kwI99UTI0qVzQ1Zo59V59Pw1p9nLiDK04apem6TQZ/kzeYKoUI 25 | K2Bvg2UHkkBAwhbJRKVYZd0ewKvbwCoKgw32vvEZr4/40GfOkOXOg2xu+/+4laYl 26 | OG93BHRrnW5Q6iAqXJHFQ3rWfHMO5OafxabJxZsOWlMvW3/woV2EoJ04HVOkeawq 27 | WqBh/vA= 28 | -----END CERTIFICATE----- 29 | -------------------------------------------------------------------------------- /checks/certificate/aiaissuers/aiaissuers.go: -------------------------------------------------------------------------------- 1 | package aiaissuers 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Authority Info Access Issuers Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check performs a strict verification on the extension according to the standard(s) 18 | func Check(d *certdata.Data) *errors.Errors { 19 | var e = errors.New(nil) 20 | 21 | // OCSP signing certificates should not any Authority Info Access Issuers 22 | if d.Type == "OCSP" { 23 | if len(d.Cert.IssuingCertificateURL) > 0 { 24 | e.Warning("OCSP signing certificate contains any Authority Info Access Issuers") 25 | } 26 | 27 | // no extra checks needed 28 | return e 29 | } 30 | 31 | // Self signed CA certificates should not contain any AIA Issuers 32 | if d.Type == "CA" && d.Cert.CheckSignatureFrom(d.Cert) == nil { 33 | if len(d.Cert.IssuingCertificateURL) != 0 { 34 | e.Warning("Self signed CA certificates should not contain any Authority Info Access Issuers") 35 | } 36 | return e 37 | } 38 | 39 | // Other certificates should contain at least one Authority Info Access Issuer 40 | if len(d.Cert.IssuingCertificateURL) == 0 { 41 | e.Err("Certificate contains no Authority Info Access Issuers") 42 | return e 43 | } 44 | 45 | for _, icu := range d.Cert.IssuingCertificateURL { 46 | l, err := url.Parse(icu) 47 | if err != nil { 48 | e.Err("Certificate contains an invalid Authority Info Access Issuer URL (%s)", icu) 49 | } 50 | if l.Scheme != "http" { 51 | e.Warning("Certificate contains a Authority Info Access Issuer with an non-preferred scheme (%s)", l.Scheme) 52 | } 53 | } 54 | 55 | return e 56 | } 57 | -------------------------------------------------------------------------------- /checks/certificate/extkeyusage/extkeyusage.go: -------------------------------------------------------------------------------- 1 | package extkeyusage 2 | 3 | import ( 4 | "crypto/x509" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Extended Key Usage Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check verifies if the the required/allowed extended keyusages 18 | // are set in relation to the certificate type. 19 | // 20 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.12 21 | // 22 | func Check(d *certdata.Data) *errors.Errors { 23 | var e = errors.New(nil) 24 | 25 | if len(d.Cert.ExtKeyUsage) == 0 && len(d.Cert.UnknownExtKeyUsage) == 0 { 26 | e.Err("Certificate contains no extended key usage") 27 | return e 28 | } 29 | 30 | for _, ku := range d.Cert.ExtKeyUsage { 31 | switch d.Type { 32 | case "DV", "OV", "EV": 33 | if ku != x509.ExtKeyUsageServerAuth && ku != x509.ExtKeyUsageClientAuth && ku != x509.ExtKeyUsageMicrosoftServerGatedCrypto { 34 | e.Err("Certificate contains an extended key usage different from ServerAuth, ClientAuth or ServerGatedCrypto") 35 | return e 36 | } 37 | case "PS": 38 | if ku != x509.ExtKeyUsageClientAuth && ku != x509.ExtKeyUsageEmailProtection { 39 | e.Err("Certificate contains an extended key usage different from ClientAuth or EmailProtection") 40 | return e 41 | } 42 | case "CS": 43 | if ku != x509.ExtKeyUsageCodeSigning { 44 | e.Err("Certificate contains an extended key usage different from ClientAuth or EmailProtection") 45 | return e 46 | } 47 | } 48 | } 49 | 50 | if len(d.Cert.UnknownExtKeyUsage) > 0 { 51 | // encryptedFileSystem 1.3.6.1.4.1.311.10.3.4 52 | //return fmt.Errorf("%s Certificate contains an unknown extented key usage", d.Type) 53 | } 54 | 55 | return e 56 | } 57 | -------------------------------------------------------------------------------- /checks/extensions/basicconstraints/basicconstraints.go: -------------------------------------------------------------------------------- 1 | package basicconstraints 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "encoding/asn1" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | ) 11 | 12 | const checkName = "BasicConstraints Extension Check" 13 | 14 | var extensionOid = asn1.ObjectIdentifier{2, 5, 29, 19} 15 | 16 | func init() { 17 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // 22 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.9 23 | // 24 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 25 | var e = errors.New(nil) 26 | 27 | // This extension MAY appear as a critical or non-critical extension in end 28 | // entity certificates. 29 | if d.Cert.IsCA { 30 | 31 | // Conforming CAs MUST include this extension in all CA certificates 32 | // that contain public keys used to validate digital signatures on 33 | // certificates and MUST mark the extension as critical in such 34 | // certificates. This extension MAY appear as a critical or non- 35 | // critical extension in CA certificates that contain public keys used 36 | // exclusively for purposes other than validating digital signatures on 37 | // certificates. Such CA certificates include ones that contain public 38 | // keys used exclusively for validating digital signatures on CRLs and 39 | // ones that contain key management public keys used with certificate 40 | // enrollment protocols. 41 | // 42 | // The CA Browser Forum BR 1.4.1 state that it should always be true for 43 | // CA certificates. 44 | if !ex.Critical { 45 | e.Err("BasicConstraints extension must be critical in CA certificates") 46 | } 47 | } 48 | 49 | return e 50 | } 51 | -------------------------------------------------------------------------------- /testdata/1024cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE9DCCA9ygAwIBAgIQAofwarY9XrQdZrXCyqJ9XzANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQG 3 | EwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2Ug 4 | QXNzb2NpYXRpb24xIjAgBgNVBAMTGVZpc2EgZUNvbW1lcmNlIElzc3VpbmcgQ0EwHhcNMTUwMjI2 5 | MDUzOTU1WhcNMTcwMjI2MDUzOTU1WjCBjjEQMA4GA1UEBxMHQmVpamluZzEQMA4GA1UECBMHQmVp 6 | amluZzELMAkGA1UEBhMCQ04xHzAdBgNVBAoTFkNoaW5hIENvbnRydWN0aW9uIEJhbmsxITAfBgNV 7 | BAsTGENoaW5hIENvbnRydWN0aW9uIEJhbmsgQTEXMBUGA1UEAxMOYWNzLmNjYi5jb20uY24wgZ8w 8 | DQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANQGcUxSvI0knjuGJ5Dh6Zt1LwJrocxJgV91hLNqWhBv 9 | NvavZVjSdeyqRaluzUXcdtbY6Zs4nk8xu9i4C10618KdEmMpQ/nvgU056QX2zVwyK/3ngKD8aJJu 10 | hNeqze3q8H3cTmorqhY0tYCxEajB9KoObxpH5f3zZyvQ5Ik1i22LAgMBAAGjggHsMIIB6DAZBgNV 11 | HREEEjAQgg5hY3MuY2NiLmNvbS5jbjBlBggrBgEFBQcBAQRZMFcwJQYIKwYBBQUHMAGGGWh0dHA6 12 | Ly9vY3NwLnZpc2EuY29tL29jc3AwLgYIKwYBBQUHMAKGImh0dHA6Ly9lbnJvbGwudmlzYWNhLmNv 13 | bS9lY29tbS5jZXIwHwYDVR0jBBgwFoAU38MqVS4vQjp6QJ2SoPdHedePh4owDAYDVR0TAQH/BAIw 14 | ADCBygYDVR0fBIHCMIG/MCigJqAkhiJodHRwOi8vRW5yb2xsLnZpc2FjYS5jb20vZUNvbW0uY3Js 15 | MIGSoIGPoIGMhoGJbGRhcDovL0Vucm9sbC52aXNhY2EuY29tOjM4OS9jbj1WaXNhIGVDb21tZXJj 16 | ZSBJc3N1aW5nIENBLGM9VVMsb3U9VmlzYSBJbnRlcm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRp 17 | b24sbz1WSVNBP2NlcnRpZmljYXRlUmV2b2NhdGlvbkxpc3QwDgYDVR0PAQH/BAQDAgM4MB0GA1Ud 18 | DgQWBBQZhFUOADNdBuZYSr9P43il7Cn/tTA5BgNVHSAEMjAwMC4GBWeBAwEBMCUwIwYIKwYBBQUH 19 | AgEWF2h0dHA6Ly93d3cudmlzYS5jb20vcGtpMA0GCSqGSIb3DQEBBQUAA4IBAQBpvUeQJTgiWSUP 20 | i4HNsLXjNwXliw/qzNbQdwJPVbOG9gvc92W8pfdDXrfoS+N5DY/fSnVXY4c6Q3kPwU5fzCcjvyZO 21 | jf7TozJTs3Bdm5uq4uCWS68JJ+fSMgsB+DTjPjs0AxL01ITkemkmzgtZRSqbLU7UtPT+k5HLwAft 22 | VHQ5twWHeDWICJZMxLyC2HrE3LT3tsfBp0NZ3NJEB9D4z4rTLYV0Xde9/+wR3UUMJ1Bsvrkp/fva 23 | oZBjZXfCNSdnBIbtwvZoHCAR/KWWQ2PucSdu3K6LYcw/XCKw9s/XVDll13Bpl0RMt6c9j7pdl/il 24 | 2P3GEvru4iE8fVEU8sK7PRi1 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /testdata/cat.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIE5DCCA8ygAwIBAgIMZmN7HUDgRLDE5uUOMA0GCSqGSIb3DQEBBQUAMH8xCzAJ 3 | BgNVBAYTAkJFMR8wHQYDVQQLExZGb3IgVGVzdCBQdXJwb3NlcyBPbmx5MRkwFwYD 4 | VQQKExBHbG9iYWxTaWduIG52LXNhMTQwMgYDVQQDEytHbG9iYWxTaWduIE9yZ2Fu 5 | aXphdGlvbiBWYWxpZGF0aW9uIENBVCAtIEcyMB4XDTE3MDgxNzA2NDgwN1oXDTE3 6 | MDgyNDA2NDgwN1owgZExCzAJBgNVBAYTAlVTMQowCAYDVQQIEwEtMQowCAYDVQQH 7 | EwEtMSEwHwYDVQQLExhEb21haW4gQ29udHJvbCBWYWxpZGF0ZWQxJjAkBgNVBAoT 8 | HVRlc3QgQ2VydGlmaWNhdGUgZm9yIE9uZUNsaWNrMR8wHQYDVQQDExZscC5vbi10 9 | cmFjZ3JvdXAuY29tLmF1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA 10 | i47s3fUmqsnEwGBjEp3/SVApv4+lC6f15O/+0os1InemKpIwlPuSBqhPm+aVOu5h 11 | u8FKLjXPyJwP8Ehy10M+UQOL5sM6YiynQpgE/2P84u+vzAvWIhIo55z1a1Wn0bQ/ 12 | TyZz9wkfbqRav4TwLD5/XVhUNd5PtYTUzbbtIqI7VL+thhsd8yKN1qLINapGk04u 13 | ypp+VmyDseD5WGw4rI/NYZr/fhm6UN/xvYxtTUR+LpWGOaAqclIUBKLWewhIv85Y 14 | r9QCiSMHwaoMoOBQ7Mm0VjVoNxUWcAGt4rZIaCqqaTNfG9ae3rhET08Bio3E+fjr 15 | 5dD00Eo3WpFmPfCV2uFPIQIDAQABo4IBSzCCAUcwDgYDVR0PAQH/BAQDAgWgMFoG 16 | CCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3NlY3VyZS5nbG9iYWxz 17 | aWduLmNvbS9jYWNlcnQvZ3Nvcmdhbml6YXRpb252YWxjYXRnMi5jcnQwTAYDVR0g 18 | BEUwQzBBBgkrBgEEAaAyARUwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xv 19 | YmFsc2lnbi5jb20vcmVwb3NpdG9yeS8wCQYDVR0TBAIwADAhBgNVHREEGjAYghZs 20 | cC5vbi10cmFjZ3JvdXAuY29tLmF1MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF 21 | BQcDAjAdBgNVHQ4EFgQU7OFmNhn2+kHa8zpb7J6dlHjVWowwHwYDVR0jBBgwFoAU 22 | wIAS7yXnVMj6Akni92/ftKsEHq8wDQYJKoZIhvcNAQEFBQADggEBAI6KbsjzLzEa 23 | 9X3FHW2BjFwoB/8pkIB4pqS0APbKHOoQSV9t61GkptAk1omQiuAWxTeuXi0FnrMe 24 | cPFuXqHV/f3TVZc9YjDLMAugTPSSne1lOJUl/Gi4J9RI5BrD9Po4nkqvSIoE/BxO 25 | mZwh3k7VLHPgQj4HrJJhg051UuvgkFXeQ6xk53NGr281lBKuiYC4SL7zOG8LcgRZ 26 | HQhVlpnKSa/pw9EqzYsKv30+b0hgReY87iufqzl/id40sCE9wnUALkb3sQ7linz/ 27 | We0rUpbOed17i43MyOZ8LUVmDJ89tuY6gZzW0yhtSuRRzdmgy3Uvi2IxUEbWz8Iw 28 | diwkLvzbUvE= 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /testdata/nokeyusage.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFCTCCA/GgAwIBAgIMWGa0zwIU3CJJw45hMA0GCSqGSIb3DQEBCwUAMGYxCzAJ 3 | BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYDVQQDEzNH 4 | bG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gU0hBMjU2IC0g 5 | RzIwHhcNMTYwOTE1MDkyNzAwWhcNMTkwODI3MTIxODEyWjBdMQswCQYDVQQGEwJO 6 | TzENMAsGA1UECBMET3NsbzENMAsGA1UEBxMET3NsbzEYMBYGA1UEChMPSW1lbnRv 7 | IE5vcmdlIEFTMRYwFAYDVQQDEw1uZXcuaW1lbnRvLm5vMIIBIjANBgkqhkiG9w0B 8 | AQEFAAOCAQ8AMIIBCgKCAQEAyuEJ26GD5KIf6/rSpB1GlZHCV6jpfqp//rqncf69 9 | 7t/OQVCgZKvMbF04ef1CiJek9/QzVQJX7hF7WxBKQ4wh4e5E3cVS4a5D4Orq1inb 10 | ELbYlXwNvfnFyl7AfzmOBRrp3ylYvr62pooE+vqPXhMS4rhKHeGaww7QTIE7+/nr 11 | +v8NYV8q7sSfzoIghqiv5eiE4WE7WPauyI0ZwyoTWpxf5RoF+lPrT4bZO6CKCkho 12 | lrqVFE6/r17FQiFexz9LIH0meQ5B0ajqFm4rc8ZQIPC6zGp8X9dFjyVdY/3wKnPm 13 | TuLOcDc7GPOTYXsc1z/Mc4L+0Bv6EkmmW6A7pgGhYEJscQIDAQABo4IBvjCCAbow 14 | gaAGCCsGAQUFBwEBBIGTMIGQME0GCCsGAQUFBzAChkFodHRwOi8vc2VjdXJlLmds 15 | b2JhbHNpZ24uY29tL2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbHNoYTJnMnIxLmNy 16 | dDA/BggrBgEFBQcwAYYzaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3Jn 17 | YW5pemF0aW9udmFsc2hhMmcyMFYGA1UdIARPME0wQQYJKwYBBAGgMgEUMDQwMgYI 18 | KwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkv 19 | MAgGBmeBDAECAjAJBgNVHRMEAjAAMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9j 20 | cmwuZ2xvYmFsc2lnbi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxzaGEyZzIuY3Js 21 | MCcGA1UdEQQgMB6CDW5ldy5pbWVudG8ubm+CDXd3dy5pbWVudG8ubm8wHQYDVR0O 22 | BBYEFD706xNuTP3rVV/3bT6BjUN1WBdKMB8GA1UdIwQYMBaAFJbeYfG9HBYpUxzA 23 | zH07gwBA5hp8MA0GCSqGSIb3DQEBCwUAA4IBAQCO6YP3RSIi1a96kAe5Ov+6T+N3 24 | bYW7ttLccGpd6mQZcFB11oOcAvVMYdXT2dVM3Iw2zHuV4BbrYVUHqgCL+026lKMn 25 | XB2vrcvqKfxcNpeLv25634R9h8z4koJs9hwZpM/sDaCrQJhBpvgB5WfXlY1uH+mi 26 | cNbomkQSQ+0DZqPSEec3ZxS/hj1TYp/BrGUSwSYVOups5YVjjPG/2ywIG0HKPN2r 27 | SeTBkudBAeSF/+zeRXMK/pHHd1tuTzZDWoAv2bPQQvF6Y3TkdyM3jE0dPWdqtR0d 28 | y7F0+xKS1YgEXL6Z17hTv1xejs8DeC7iTvCyDxFyHxYc3qdsJ8UJ4GUDX8rB 29 | -----END CERTIFICATE----- 30 | -------------------------------------------------------------------------------- /testdata/ivissues.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFETCCA/mgAwIBAgIQXIzdnFoDG0PAHrE0Ad90njANBgkqhkiG9w0BAQsFADB4 3 | MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEpMCcGA1UECxMg 4 | U3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxJjAkBgNVBAMTHVN0YXJ0 5 | Q29tIENsYXNzIDIgSVYgU2VydmVyIENBMB4XDTE2MDQyMjExNTA0MloXDTE4MDQy 6 | MjExNTA0MlowPTELMAkGA1UEBhMCQkUxFTATBgNVBAoMDEJydW5vIFBlbHplcjEX 7 | MBUGA1UEAwwOKi5lLXBlbHplci5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw 8 | ggEKAoIBAQDpN6sf1hkc9VS6oGEXYkZuFOhdMSzpnQ/W03d2FcOEV5LBcl5Pf+Nn 9 | BUkfFR81VL5PTwMNJN01vHtNtaskd49+a39m4cVEN169eWLPNZXO0Olbu90Zfmxg 10 | kj5mxPBAfJhgDqHA0bYAgsZ55hsdQhOnZsMbna0YVezMGJWfGavsdfvRiCz/Wlgw 11 | UnXd8tAf1NmFB1bVb7OvEkQjdeWZC7Gbr6B5ulIG/ojTydk3ANFB2Y4T31pi7mzY 12 | dHK0i1zDjl+UAuSYxXwNgbhWTHpZ8woc5SeTlYi2eV0+FwgCsuykMObel8keDs93 13 | mmHkIC41rt+sGYNNkZtHBOY+FC3VG8U/AgMBAAGjggHQMIIBzDAOBgNVHQ8BAf8E 14 | BAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAkGA1UdEwQCMAAw 15 | HQYDVR0OBBYEFCgDKQIScL5ySRZ2Fjrby5TBWGybMB8GA1UdIwQYMBaAFJTehUEq 16 | pdlF9mAsLkyTCaYsI34+MG8GCCsGAQUFBwEBBGMwYTAkBggrBgEFBQcwAYYYaHR0 17 | cDovL29jc3Auc3RhcnRzc2wuY29tMDkGCCsGAQUFBzAChi1odHRwOi8vYWlhLnN0 18 | YXJ0c3NsLmNvbS9jZXJ0cy9zY2Euc2VydmVyMi5jcnQwOAYDVR0fBDEwLzAtoCug 19 | KYYnaHR0cDovL2NybC5zdGFydHNzbC5jb20vc2NhLXNlcnZlcjIuY3JsMBkGA1Ud 20 | EQQSMBCCDiouZS1wZWx6ZXIuY29tMCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3Rh 21 | cnRzc2wuY29tLzBQBgNVHSAESTBHMAgGBmeBDAECAzA7BgsrBgEEAYG1NwECBTAs 22 | MCoGCCsGAQUFBwIBFh5odHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kwEwYK 23 | KwYBBAHWeQIEAwEB/wQCBQAwDQYJKoZIhvcNAQELBQADggEBAESgEc7xvYLYSYVH 24 | 1GvilKik1a3Cu4N2GB06CwMXhlqzN//SfPspOSNgSIhhY9kOX/tttpF3RmjhUhyg 25 | mX07Yso7kKa1LjefJBu9UhBslrfXxqq+T/+GfKt1sa1P88d4MlcvYTMF3aNxMAK4 26 | lE/12JupUm0weQBiA55lINowxlKFxpLvKihNSdV27HADZ/JPzegjmyIWLCn64u9J 27 | laH9NY0XEX89yfWLwg2wQzzHfyxXNPArHh8tCy6Eh1nZBni/7hd5GCULl4aBayUp 28 | 77dwnpz+4ULMfKavg1fErOxTT/qGZdBVzvA2IdvZkKVx8flaI8+Btbq857F6cSBK 29 | tcw0FFw= 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /testdata/invalid_encoding2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFJjCCBA6gAwIBAgIQJdXzFcapPxvNaCu/7HJpfTANBgkqhkiG9w0BAQsFADBH 3 | MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMX 4 | R2VvVHJ1c3QgRVYgU1NMIENBIC0gRzQwHhcNMTQwNDI5MDAwMDAwWhcNMTYwNDI4 5 | MjM1OTU5WjCB3TETMBEGCysGAQQBgjc8AgEDEwJERTEcMBoGCysGAQQBgjc8AgEB 6 | FAtEw7xzc2VsZG9yZjEdMBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24xEjAQ 7 | BgNVBAUTCUhSQiA1MTUxNjELMAkGA1UEBhMCREUxHDAaBgNVBAgME05vcmRyaGVp 8 | biBXZXN0ZmFsZW4xFDASBgNVBAcMC0TDvHNzZWxkb3JmMRMwEQYDVQQKDApzaW15 9 | byBHbWJIMQswCQYDVQQLDAJJVDESMBAGA1UEAwwJc2F5aGV5LmRlMIIBIjANBgkq 10 | hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuIUh3bi7EHXZ5lr2rk9TyzHZXgO4BD42 11 | d7pDQ8hYQsA/EIDTCSU2Vp8FEe+7cJbxKp9hMu9/OY6FNddn7hjJ40/64LW2yzbs 12 | I0cKfMCALub2fppreUJi0plK25zxof0E5PFS33bM/H+Q4H8NNbzUo64CnJfsp/jQ 13 | LO5zUUj5Jh98g4lV/OwB7pWS7RkIudidTXyYDvnjUh19tH2RNreJ+2z1i7H8EAVB 14 | BfXIAXJE57jdonXWONLjZBR3jFSIjKwIfAjD6Pv9VXUj7ua/uwJhw2QMAFDtF4uc 15 | TStIQPoDfBsRbgbRA4e2mr9BgW3SEwGGyYLeFlX/pgiWISBuyWP6MwIDAQABo4IB 16 | dTCCAXEwCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwLQYDVR0fBCYwJDAioCCg 17 | HoYcaHR0cDovL2dtMS5zeW1jYi5jb20vZ20xLmNybDBkBgNVHSAEXTBbMFkGCSsG 18 | AQQB8CIBBjBMMCMGCCsGAQUFBwIBFhdodHRwczovL2Quc3ltY2IuY29tL2NwczAl 19 | BggrBgEFBQcCAjAZFhdodHRwczovL2Quc3ltY2IuY29tL3JwYTAdBgNVHSUEFjAU 20 | BggrBgEFBQcDAQYIKwYBBQUHAwIwHwYDVR0jBBgwFoAU3s9cULeuAh8VF6oW6A21 21 | KJ1qWvMwWgYIKwYBBQUHAQEETjBMMCAGCCsGAQUFBzABhhRodHRwOi8vZ20yLnN5 22 | bWNiLmNvbTAoBggrBgEFBQcwAoYcaHR0cDovL2dtMS5zeW1jYi5jb20vZ20xLmNy 23 | dDAjBgNVHREEHDAagg13ZWIuc2F5aGV5LmRlgglzYXloZXkuZGUwDQYJKoZIhvcN 24 | AQELBQADggEBABAp8in9Cbvtr83c/3dXQjBUdtlrD3gKsYlelPsa0IVXMVj9gool 25 | nfqazQLLW1yZt2lFmaXyIYZAp1AcT6pJHh+qVk0mdHS4tGc7iBnZR9WOzjd+dslb 26 | PWPZ+AA9wzYgmehkXf61AjDwzb5XZ1Z2SYCKAArSI41s0fOYVPM9+HUImVpLVGhQ 27 | j+DMnfAwdqtUmn3KnNGk/7Z77o+D34c5uoFWR2DPwx4UvwBJFw/ctAco6xUaufuB 28 | bcpTvrgk+6rWo/28Nm587dR9o4Rxvxiwb++io9HVOlHvyAQyye0YPlFQi61LzXq6 29 | xGjMbsYGxOFSFEW20nRpX9/LbR20nMYUbPk= 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /testdata/metadata.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFPDCCBCSgAwIBAgIMP0Tw4Y+s0EauWnkMMA0GCSqGSIb3DQEBCwUAMGYxCzAJ 3 | BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYDVQQDEzNH 4 | bG9iYWxTaWduIE9yZ2FuaXphdGlvbiBWYWxpZGF0aW9uIENBIC0gU0hBMjU2IC0g 5 | RzIwHhcNMTcwODE1MDI1MjIwWhcNMTgxMDAzMTQ1OTU5WjBxMQswCQYDVQQGEwJK 6 | UDEOMAwGA1UECAwFVG9reW8xEDAOBgNVBAcMB0tvdG8ta3UxCjAIBgNVBAsMAV8x 7 | HTAbBgNVBAoMFE5UVCBEQVRBIENPUlBPUkFUSU9OMRUwEwYDVQQDDAxvbW5pcGZy 8 | cC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC8EW/f9BOkdh9c 9 | 5pHRVUQjcK9m+/5EcamNv0FyHHcgjAQZ+lFC36DEOnfH8dmEfeKhVexrgEiQSH/Y 10 | c4eRxvfCLaLGyylFebAgINoKQL9tFOdMbMkBQv9UmNvmdhsK3aIeMTtvbAGBYzsB 11 | z+ohYLiAACy3Vy/RrpIPI8+H3Z16VVnZRiJELAA5pMjS9NwPpFLjSNb/31yy6ubR 12 | IYpmGrLkfq2XLPtcXxWiyaAxbchPj232EO+ee6PHTJq04oukckU6u5w66HV0usKx 13 | OdPcHxMBxPOGNDoaid2/m4K6heb6xbkv3vbS8y7OCjNzj1k1gR/bOxibgGOGdATZ 14 | ve7TR10PAgMBAAGjggHdMIIB2TAOBgNVHQ8BAf8EBAMCBaAwgaAGCCsGAQUFBwEB 15 | BIGTMIGQME0GCCsGAQUFBzAChkFodHRwOi8vc2VjdXJlLmdsb2JhbHNpZ24uY29t 16 | L2NhY2VydC9nc29yZ2FuaXphdGlvbnZhbHNoYTJnMnIxLmNydDA/BggrBgEFBQcw 17 | AYYzaHR0cDovL29jc3AyLmdsb2JhbHNpZ24uY29tL2dzb3JnYW5pemF0aW9udmFs 18 | c2hhMmcyMFYGA1UdIARPME0wQQYJKwYBBAGgMgEUMDQwMgYIKwYBBQUHAgEWJmh0 19 | dHBzOi8vd3d3Lmdsb2JhbHNpZ24uY29tL3JlcG9zaXRvcnkvMAgGBmeBDAECAjAJ 20 | BgNVHRMEAjAAMEkGA1UdHwRCMEAwPqA8oDqGOGh0dHA6Ly9jcmwuZ2xvYmFsc2ln 21 | bi5jb20vZ3MvZ3Nvcmdhbml6YXRpb252YWxzaGEyZzIuY3JsMBcGA1UdEQQQMA6C 22 | DG9tbmlwZnJwLmNvbTAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYD 23 | VR0OBBYEFO1MUn7FM0nHSDQjNgzehBajkhjIMB8GA1UdIwQYMBaAFJbeYfG9HBYp 24 | UxzAzH07gwBA5hp8MA0GCSqGSIb3DQEBCwUAA4IBAQAFinjtU+QQYOFyjZCmsrda 25 | yRNz0lIRht0ftVbxgplvo63kjtGbzCdSKsZLEcCSrScc+sqgUGQGouosZeMO77tx 26 | wILkONvgmUO2jFMHzxYRsi5QD2MDnaHbgPv3dxhHceGAGbeQT8UDQwQPuDiNq5No 27 | YeScZ/iblfvDSDZnZP32aC0ddaJUfSyRxdpktID+Xwuqd/c2nKGTC9CQ+JE2kEZF 28 | /NHTisswXbPeIdpEvrMToI9Z6qRaEaX+sszRxjR0skoKuvli9OnbVEW4ZvHgEEmA 29 | tHpnXKrVbA74eYpKeAtPgh9+V2qDj4W1G5e78B7IEDn024OJyeq6t5Y00EevHPj+ 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /checks/certificate/revocation/revocation.go: -------------------------------------------------------------------------------- 1 | package revocation 2 | 3 | import ( 4 | "net/url" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Certificate Revocation Information Check" 12 | 13 | func init() { 14 | checks.RegisterCertificateCheck(checkName, nil, Check) 15 | } 16 | 17 | // Check performs a strict verification on the extension according to the standard(s) 18 | func Check(d *certdata.Data) *errors.Errors { 19 | var e = errors.New(nil) 20 | 21 | // OCSP signing certificates should not contain an OCSP server 22 | if d.Type == "OCSP" { 23 | if len(d.Cert.OCSPServer) > 0 { 24 | e.Warning("OCSP signing certificate contains an OCSP server") 25 | } 26 | 27 | // no extra checks needed 28 | return e 29 | } 30 | 31 | // Self signed CA certificates should not contain any revocation sources 32 | if d.Type == "CA" && d.Cert.CheckSignatureFrom(d.Cert) == nil { 33 | if len(d.Cert.CRLDistributionPoints) != 0 && len(d.Cert.OCSPServer) != 0 { 34 | e.Warning("Self signed CA certificates should not contain any revocation sources") 35 | } 36 | return e 37 | } 38 | 39 | if len(d.Cert.CRLDistributionPoints) == 0 && len(d.Cert.OCSPServer) == 0 { 40 | e.Err("Certificate contains no CRL or OCSP server") 41 | return e 42 | } 43 | 44 | // Check CRL information 45 | for _, crl := range d.Cert.CRLDistributionPoints { 46 | l, err := url.Parse(crl) 47 | if err != nil { 48 | e.Err("Certificate contains an invalid CRL (%s)", crl) 49 | } else if l.Scheme != "http" { 50 | e.Err("Certificate contains a CRL with an non-preferred scheme (%s)", l.Scheme) 51 | } 52 | } 53 | 54 | // Check OCSP information 55 | for _, server := range d.Cert.OCSPServer { 56 | s, err := url.Parse(server) 57 | if err != nil { 58 | e.Err("Certificate contains an invalid OCSP server (%s)", s) 59 | } else if s.Scheme != "http" { 60 | e.Err("Certificate contains a OCSP server with an non-preferred scheme (%s)", s.Scheme) 61 | } 62 | } 63 | 64 | return e 65 | } 66 | -------------------------------------------------------------------------------- /checks/extensions/ocspmuststaple/ocspmuststaple.go: -------------------------------------------------------------------------------- 1 | package ocspmuststaple 2 | 3 | import ( 4 | "bytes" 5 | "crypto/x509/pkix" 6 | "encoding/asn1" 7 | "fmt" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | "github.com/globalsign/certlint/checks" 11 | "github.com/globalsign/certlint/errors" 12 | ) 13 | 14 | const ( 15 | checkName = "OCSP Must Staple Extension Check" 16 | certTypeErr = "OCSP Must Staple extension set in non end-entity/issuer certificate" 17 | critExtErr = "OCSP Must Staple extension set critical" 18 | ) 19 | 20 | var ( 21 | // RFC 7633 OID of the OCSP Must Staple TLS Extension Feature 22 | extensionOid = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24} 23 | // Expected extension value (DER encoded ASN.1 bytestring) 24 | expectedExtensionValue = []uint8{0x30, 0x3, 0x2, 0x1, 0x5} 25 | extValueErr = fmt.Sprintf( 26 | "OCSP Must Staple extension had incorrect value. "+ 27 | "Should be ASN.1 DER %#v", expectedExtensionValue) 28 | ) 29 | 30 | // RFC 7633 only defines this extension for PKIX end-entity certificates, 31 | // certificate signing requests, and certificate signing certificates (CAs). 32 | // We should not allow it for cert types like "OCSP", "PS", "CS", etc. 33 | var allowedCertTypes = map[string]bool{ 34 | "DV": true, 35 | "OV": true, 36 | "EV": true, 37 | "CA": true, 38 | } 39 | 40 | func init() { 41 | // Register this check for the OCSP Must Staple extension OID. 42 | checks.RegisterExtensionCheck(checkName, extensionOid, nil, Check) 43 | } 44 | 45 | // Check performs a strict verification on the extension according to the standard(s) 46 | func Check(ex pkix.Extension, d *certdata.Data) *errors.Errors { 47 | var e = errors.New(nil) 48 | 49 | // If the cert type isn't one of the `allowedCertTypes`, return an error 50 | if _, allowed := allowedCertTypes[d.Type]; !allowed { 51 | e.Err(certTypeErr) 52 | } 53 | 54 | // Per RFC 7633 "The TLS feature extension SHOULD NOT be marked critical" 55 | if ex.Critical { 56 | e.Err(critExtErr) 57 | } 58 | 59 | // Check that the extension value is the expected slice of DER encoded ASN.1 60 | if bytes.Compare(ex.Value, expectedExtensionValue) != 0 { 61 | e.Err(extValueErr) 62 | } 63 | 64 | return e 65 | } 66 | -------------------------------------------------------------------------------- /testdata/evissues.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIF0TCCBLmgAwIBAgILBoY2yGC8oNlKsr4wDQYJKoZIhvcNAQELBQAwYDELMAkG 3 | A1UEBhMCREUxETAPBgNVBAsTCFZSIElERU5UMRwwGgYDVQQKDBNGSURVQ0lBICYg 4 | R0FEIElUIEFHMSAwHgYDVQQDExdWUiBJREVOVCBFViBTU0wgQ0EgMjAxNjAeFw0x 5 | NjA0MTkxMTQwNDVaFw0xODA3MTkyMTU5NTlaMIGKMQswCQYDVQQGEwJERTEQMA4G 6 | A1UECAwHSEFNQlVSRzEQMA4GA1UEBwwHSEFNQlVSRzEtMCsGA1UECgwkRE9OTkVS 7 | ICYgUkVVU0NIRUwgQUtUSUVOR0VTRUxMU0NIQUZUMREwDwYDVQQLDAhWUi1JREVO 8 | VDEVMBMGA1UEAwwMVzEuRE9OTkVSLkRFMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 9 | MIIBCgKCAQEA4b7K1kS/MEqMlXC1wt2A31j5EoHTtN2dDW3E8ZCwCgEn/tPq15zP 10 | Z1jOwrIaBAw/hJ1DZHC82btC99pXylnRlniPV7hiiMVR0TdEbTnOaum7Gwz8IGS/ 11 | OwKRfgSjZ3TExcPGDujALyJ17C59pBKQ2+Mdr5DzG3DyWmBXfrODcUAyWjgCIAta 12 | gJ3AroSpsvyrKMa8lEHzpDNbrKgULOsDFF56lH8O6E3cKF/cnIC53w6cyuwFQSDG 13 | IpOrS1nr4FX7Xt/wAkuFArRhUGHEBNnYb9aFVhEfgX2BUtGJTeBEMIegyuj2ThrL 14 | ZUpbRRYh9qY3ne7Feh5RlEgXrFw0CAWuhQIDAQABo4ICXzCCAlswCQYDVR0TBAIw 15 | ADCBiQYIKwYBBQUHAQEEfTB7MHkGCCsGAQUFBzABhm1odHRwOi8vb2NzcC52ci1p 16 | ZGVudC5kZS9ndG5vY3NwL09DU1BSZXNwb25kZXIvRklEVUNJQSUyMCUyNiUyMEdB 17 | RCUyMElUJTIwQUcvVlIlMjBJREVOVCUyMEVWJTIwU1NMJTIwQ0ElMjAyMDE2MBcG 18 | A1UdEQQQMA6CDFcxLkRPTk5FUi5ERTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0OBBYE 19 | FMAvrslD1x8xxZZqEME0Iwv/Na59MBMGA1UdJQQMMAoGCCsGAQUFBwMBMBMGCisG 20 | AQQB1nkCBAMBAf8EAgUAMC0GA1UdIwQmMCSAIlBST0QuR1ROLkVWU1NMQ0EuU0lH 21 | R0VOUlMuMDAwMDI3MDAwgaMGA1UdIASBmzCBmDA0BgwrBgEEAYGKIAQBAQowJDAi 22 | BggrBgEFBQcCARYWaHR0cDovL3d3dy52ci1pZGVudC5kZTBGBgwrBgEEAb5YAAJk 23 | AQIwNjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5xdW92YWRpc2dsb2JhbC5jb20v 24 | cmVwb3NpdG9yeTAPBg0rBgEEAb5YAAKMQAACMAcGBWeBDAEBMHsGA1UdHwR0MHIw 25 | cKBuoGyGamh0dHA6Ly93d3cudnItaWRlbnQuZGUvZ3RuY3JsL0NSTFJlc3BvbmRl 26 | ci9GSURVQ0lBJTIwJTI2JTIwR0FEJTIwSVQlMjBBRy9WUiUyMElERU5UJTIwRVYl 27 | MjBTU0wlMjBDQSUyMDIwMTYwDQYJKoZIhvcNAQELBQADggEBADmkZfSTUJVotuWH 28 | 85k5sUpxBJ/qYu3NF9nbntIIyT6EVqLw4K3U8EIdo1O0UA6Lr7lgTAO81OTUId9V 29 | XkbXw8OTzIPFip7HRNmXL+UF0O2M1MMaOgiGBZAXWVJSq3mR9FVnzLWZY5IagMGc 30 | fpKI/1dtejLUfPmanxStTFmS/t14W0YzvFLdQQPqtUuziFjlLm6BiBmm7MZhkLsk 31 | vBTVp35U5dIag5N8nQsIs9/iTCCf2HCYbEIK8KAOqVx0X7FuuJCTe8VVMw38iIob 32 | cfCydo7jtgk3Yt6lI5XRM1+5T8QzdR+1HfHFkEf7TtAT7KnigQZ3oC7ugTYFxU45 33 | T5Lhkjg= 34 | -----END CERTIFICATE----- 35 | -------------------------------------------------------------------------------- /testdata/2043bit.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGWzCCBUOgAwIBAgIDD9KiMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJTDEWMBQGA1UE 3 | ChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2ln 4 | bmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3MgMSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2 5 | ZXIgQ0EwHhcNMTQwNDEzMTEzODQxWhcNMTUwNDE0MDQyNjQ4WjBsMRkwFwYDVQQNExBFS3pXUTIw 6 | TEplTFhPYUpFMQswCQYDVQQGEwJERTEaMBgGA1UEAxMRbWFpbC5pLXQtY2xvdWQuZGUxJjAkBgkq 7 | hkiG9w0BCQEWF3Bvc3RtYXN0ZXJAaS10LWNsb3VkLmRlMIIBITANBgkqhkiG9w0BAQEFAAOCAQ4A 8 | MIIBCQKCAQAFpnC9oi1ots0BXhSTCFVXYHJTLI9I42EuE4h6GcgI6iIQl0QNAtHlc8TgSxBvo8GN 9 | akJIS7pl+oTqT0qKTThiB87wpsytDFTMDpMfiZCMVRGDKa13vIIyR1fF23g4+14sBfRZnL06jadw 10 | DeOcwxptNu9qg8VtQa19QF3j0xf+MApPVvxR5F8+Z9V0++o1VMltaLpefX1Se9ULDW7gPoBC1n4r 11 | U5IHJh9PHCG4CElGClqneWRILzdju3HtdT1axq6UvYzkg5vg7hDdmwsUNOY/qavzoQzMgX/Cgv1k 12 | fJFtGsnDNgnKi5FkTdgBYrYdkbmgm4CJrjI5LG//N+0ealrdAgMBAAGjggLkMIIC4DAJBgNVHRME 13 | AjAAMAsGA1UdDwQEAwIDqDATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNVHQ4EFgQUTxD4fMLX0Yx5 14 | kjlGK620AY7FUXgwHwYDVR0jBBgwFoAU60I00Jiwq5/0G2sI98xkLu8OLEUwKgYDVR0RBCMwIYIR 15 | bWFpbC5pLXQtY2xvdWQuZGWCDGktdC1jbG91ZC5kZTCCAVYGA1UdIASCAU0wggFJMAgGBmeBDAEC 16 | ATCCATsGCysGAQQBgbU3AQIDMIIBKjAuBggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5j 17 | b20vcG9saWN5LnBkZjCB9wYIKwYBBQUHAgIwgeowJxYgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBB 18 | dXRob3JpdHkwAwIBARqBvlRoaXMgY2VydGlmaWNhdGUgd2FzIGlzc3VlZCBhY2NvcmRpbmcgdG8g 19 | dGhlIENsYXNzIDEgVmFsaWRhdGlvbiByZXF1aXJlbWVudHMgb2YgdGhlIFN0YXJ0Q29tIENBIHBv 20 | bGljeSwgcmVsaWFuY2Ugb25seSBmb3IgdGhlIGludGVuZGVkIHB1cnBvc2UgaW4gY29tcGxpYW5j 21 | ZSBvZiB0aGUgcmVseWluZyBwYXJ0eSBvYmxpZ2F0aW9ucy4wNQYDVR0fBC4wLDAqoCigJoYkaHR0 22 | cDovL2NybC5zdGFydHNzbC5jb20vY3J0MS1jcmwuY3JsMIGOBggrBgEFBQcBAQSBgTB/MDkGCCsG 23 | AQUFBzABhi1odHRwOi8vb2NzcC5zdGFydHNzbC5jb20vc3ViL2NsYXNzMS9zZXJ2ZXIvY2EwQgYI 24 | KwYBBQUHMAKGNmh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2NlcnRzL3N1Yi5jbGFzczEuc2VydmVy 25 | LmNhLmNydDAjBgNVHRIEHDAahhhodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS8wDQYJKoZIhvcNAQEF 26 | BQADggEBAJ3JuVeRGD9MBldq6jEOFvDBe7/DSRnGRaGEZ2TWnDFxzcqMueMBuGQP101NiOZ8/L3G 27 | GTtqdrpynUR6p3fA8iiM26nVNcx3fs44X44TrpuSKtChPUGcDzaPUrw8+MXSi7kB3bEsoTnc/6ad 28 | iffb9t80jHRfIuonfvJHf3alMWNH+xAlJsva6U9O/anibbKaxNw62Tb5LMcIAM7wrnh5NkUgcEMD 29 | NtyNqyG10mS8MDhc9KOJDO7PJOiFOmNKtsOw/wdokmo49JkOOiualsmHYVIrRPUl+LOrvItnqGDx 30 | mZWE3eLaWu+Qr8xOQ6eofY7FxFfwspKsW5+mdynhIJzCYYQ= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /checks/certificate/keyusage/keyusage.go: -------------------------------------------------------------------------------- 1 | package keyusage 2 | 3 | import ( 4 | "crypto/dsa" 5 | "crypto/ecdsa" 6 | "crypto/rsa" 7 | "crypto/x509" 8 | 9 | "github.com/globalsign/certlint/certdata" 10 | "github.com/globalsign/certlint/checks" 11 | "github.com/globalsign/certlint/errors" 12 | ) 13 | 14 | const checkName = "Key Usage Check" 15 | 16 | func init() { 17 | checks.RegisterCertificateCheck(checkName, nil, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | // checkKeyUsageExtension verifies if the the required/allowed keyusages are set 22 | // 23 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.3 24 | // 25 | // TODO: Check if we can or need to do something with dh.PublicKey 26 | func Check(d *certdata.Data) *errors.Errors { 27 | var e = errors.New(nil) 28 | var forbidden []x509.KeyUsage 29 | 30 | // Source 31 | // https://github.com/awslabs/certlint/blob/master/lib/certlint/extensions/keyusage.rb 32 | switch d.Cert.PublicKey.(type) { 33 | case *rsa.PublicKey: 34 | forbidden = []x509.KeyUsage{ 35 | x509.KeyUsageKeyAgreement, 36 | x509.KeyUsageEncipherOnly, 37 | x509.KeyUsageDecipherOnly, 38 | } 39 | case *ecdsa.PublicKey: 40 | forbidden = []x509.KeyUsage{ 41 | x509.KeyUsageKeyEncipherment, 42 | x509.KeyUsageDataEncipherment, 43 | } 44 | case *dsa.PublicKey: 45 | forbidden = []x509.KeyUsage{ 46 | x509.KeyUsageKeyEncipherment, 47 | x509.KeyUsageDataEncipherment, 48 | x509.KeyUsageKeyAgreement, 49 | x509.KeyUsageEncipherOnly, 50 | x509.KeyUsageDecipherOnly, 51 | } 52 | // case *dh.PublicKey: 53 | // forbidden = []x509.KeyUsage{ 54 | // x509.KeyUsageDigitalSignature, 55 | // x509.KeyUsageContentCommitment, 56 | // x509.KeyUsageKeyEncipherment, 57 | // x509.KeyUsageDataEncipherment, 58 | // x509.KeyUsageCertSign, 59 | // x509.KeyUsageCRLSign, 60 | // } 61 | } 62 | 63 | // If we have not defined this certificate as a CA certificate, the following 64 | // key usages would not be allowed 65 | if d.Type != "CA" { 66 | if d.Cert.KeyUsage == 0 { 67 | e.Err("Certificate has no key usage set") 68 | return e 69 | } 70 | 71 | forbidden = append(forbidden, x509.KeyUsageCertSign) 72 | forbidden = append(forbidden, x509.KeyUsageCRLSign) 73 | } 74 | 75 | // Check if there are any forbidden key usages set 76 | for _, fku := range forbidden { 77 | if d.Cert.KeyUsage&fku != 0 { 78 | e.Err("Certificate has key usage %s set", keyUsageString(fku)) 79 | } 80 | } 81 | 82 | return e 83 | } 84 | -------------------------------------------------------------------------------- /testdata/invalid_encoding1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGozCCBYugAwIBAgIQZN25aW18epqKuyr57PUjKTANBgkqhkiG9w0BAQUFADCB 3 | vjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL 4 | ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug 5 | YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMv 6 | VmVyaVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0Ew 7 | HhcNMTQwNTI2MDAwMDAwWhcNMTUwODAzMjM1OTU5WjCCASMxEzARBgsrBgEEAYI3 8 | PAIBAxMCQ04xGjAYBgsrBgEEAYI3PAIBAhQJ5LiK5rW35biCMRowGAYLKwYBBAGC 9 | NzwCAQEUCeS4iua1t+W4gjEdMBsGA1UEDxMUUHJpdmF0ZSBPcmdhbml6YXRpb24x 10 | GDAWBgNVBAUTDzMxMDEwNDAwMDU0Mjc5MTELMAkGA1UEBhMCQ04xEjAQBgNVBAgM 11 | CeS4iua1t+W4gjESMBAGA1UEBwwJ5b6Q5rGH5Yy6MTMwMQYDVQQKDCrkuprmlbDk 12 | v6Hmga/np5HmioDvvIjkuIrmtbfvvInmnInpmZDlhazlj7gxFTATBgNVBAsMDOS6 13 | mua0suivmuS/oTEaMBgGA1UEAwwRd3d3LnRydXN0YXNpYS5jb20wggEiMA0GCSqG 14 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDd/HN3zQbtsWVLQPnleRaGXoVfVNlxcegq 15 | 0KNYFM59KGnoKeY+joTedwFL3XHyqhYRMe9qsnZ6a6TmnhsmruSKCuynmClkqcn5 16 | AYg15MiJpceLYrJko4CMUZfoJwn3YNrWPMuLkey9pskHn630KQCHr/7URlQwKGUE 17 | CKtvEZupqooc6I5ftPViKr/nzc1w0Hof9yMVE7abPgUD/1V1G7rkWBpyDtWrT2Ny 18 | k+nzWQQRHZCrOwBhWpn8NyE/0Ar15RxEnZNdG9iD4EdD5KaMHrjwICnIwYtD0Mz8 19 | y1fbHVIkL4aGFxkRMXTqXwrSw5ERnTl+onYE34oX3BX3PDb6gUObAgMBAAGjggIz 20 | MIICLzAJBgNVHRMEAjAAMA4GA1UdDwEB/wQEAwIFoDBmBgNVHSAEXzBdMFsGC2CG 21 | SAGG+EUBBxcGMEwwIwYIKwYBBQUHAgEWF2h0dHBzOi8vZC5zeW1jYi5jb20vY3Bz 22 | MCUGCCsGAQUFBwICMBkWF2h0dHBzOi8vZC5zeW1jYi5jb20vcnBhMCsGA1UdHwQk 23 | MCIwIKAeoByGGmh0dHA6Ly9zYi5zeW1jYi5jb20vc2IuY3JsMCgGA1UdJQQhMB8G 24 | CCsGAQUFBwMBBggrBgEFBQcDAgYJYIZIAYb4QgQBMB8GA1UdIwQYMBaAFE5DyB12 25 | 7zdTek/yWG+U8zji1b3fMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYTaHR0 26 | cDovL3NiLnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL3NiLnN5bWNiLmNv 27 | bS9zYi5jcnQwgdgGA1UdEQSB0DCBzYISbXBraS50cnVzdGFzaWEuY29tghZzc2xj 28 | bG91ZC50cnVzdGFzaWEuY29tghFhcGkudHJ1c3Rhc2lhLmNvbYIWc3NsdG9vbHMu 29 | dHJ1c3Rhc2lhLmNvbYIUc2VjdXJlLnRydXN0YXNpYS5jb22CFnBhc3Nwb3J0LnRy 30 | dXN0YXNpYS5jb22CE29yZGVyLnRydXN0YXNpYS5jb22CD20udHJ1c3Rhc2lhLmNv 31 | bYINdHJ1c3Rhc2lhLmNvbYIRd3d3LnRydXN0YXNpYS5jb20wDQYJKoZIhvcNAQEF 32 | BQADggEBALgsmLWrKVxJ51O/HcZkc3aWCgQPkbt7hE92kksTpvKaF/tSpUXBSBin 33 | bBxsnRrfqK27kiM/j9aKrPwjaObzQ3DRw/VeDApSpgmKlV62CD8A5A7i2/XZS+KH 34 | cT5CMqkLrCz+vfdHhcfBF3ZyW2VKkqJgnvuzxMnfrMb61RehoLUGBuOeJ+hvHfsx 35 | hSC3b0JAm9mEgoQ5iw/Q+sdXJmztuvNdGaKbPiX5zKEdvJb7vdmMwUc5RWIefvb6 36 | gk/PHr39UqCQTHCQAKsbVGM9yWxkn+JgZKfNHM/SiSgfWzr1zN9JER52aj6uZnKT 37 | pHv47y4Ue7cpei1UXr0jdqN/eijwtYo= 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /testdata/exponent1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIGrTCCBZWgAwIBAgIDAlRTMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ 3 | TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0 4 | YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg 5 | MiBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTQwNzE3MTg1ODA2 6 | WhcNMTYwNzE3MDg1MTE4WjCBtDEZMBcGA1UEDRMQMjFObWFaeWtuNEkxeVpuNDEL 7 | MAkGA1UEBhMCR0IxEjAQBgNVBAgTCUhhbXBzaGlyZTEWMBQGA1UEBxMNV2F0ZXJs 8 | b292aWxsZTEbMBkGA1UEChMSTHVrZSBHcmFuZ2VyLUJyb3duMRwwGgYDVQQDExNz 9 | ZWN1cmUtMS5sdWtlZ2IuY29tMSMwIQYJKoZIhvcNAQkBFhR3ZWJtYXN0ZXJAbHVr 10 | ZWdiLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAM+Gmn1cn70z 11 | u8KxBqg+xRjzAQTdejgOjo0QqvhkSYKmFp3Zrl5/m1PLuynamEcmiC4dZLO8fpY6 12 | p9aH9vU/pzvTxdVhPGMF+bxkHXFl9cjoZEE1iIFrKiS73Z91T+o15TJ2Wot6tZJl 13 | NLeIQl1BC9EALUNHVWA8DmAEXIgTx0JVFjEygbreqVbr22Z/MbrohxrMrZCGS6dt 14 | 1cG352dWQfcDswlhY7WwGXvFkciWW2qAoVMPmke1mkRTvZPj5M4MFxFRHf1sdOTs 15 | Ks5XJ8yDmAgyLNV1qSf+ql5IyUaaKT/mAU2XSnDRXfjACyPLvvVwC8LywDOcxIs5 16 | fj3GIzmamN0CAQGjggLuMIIC6jAJBgNVHRMEAjAAMAsGA1UdDwQEAwIDqDAdBgNV 17 | HSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwEwHQYDVR0OBBYEFC5MPLrQI2dDrJZ3 18 | if4Q1R1hwgAXMB8GA1UdIwQYMBaAFBHbI0X9VMxqcW+EigPXvvcBLyaGMCoGA1Ud 19 | EQQjMCGCE3NlY3VyZS0xLmx1a2VnYi5jb22CCmx1a2VnYi5jb20wggFWBgNVHSAE 20 | ggFNMIIBSTAIBgZngQwBAgIwggE7BgsrBgEEAYG1NwECAzCCASowLgYIKwYBBQUH 21 | AgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwgfcGCCsGAQUF 22 | BwICMIHqMCcWIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MAMCAQEa 23 | gb5UaGlzIGNlcnRpZmljYXRlIHdhcyBpc3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBD 24 | bGFzcyAyIFZhbGlkYXRpb24gcmVxdWlyZW1lbnRzIG9mIHRoZSBTdGFydENvbSBD 25 | QSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkgZm9yIHRoZSBpbnRlbmRlZCBwdXJwb3Nl 26 | IGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJlbHlpbmcgcGFydHkgb2JsaWdhdGlvbnMu 27 | MDUGA1UdHwQuMCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydDIt 28 | Y3JsLmNybDCBjgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYtaHR0cDovL29j 29 | c3Auc3RhcnRzc2wuY29tL3N1Yi9jbGFzczIvc2VydmVyL2NhMEIGCCsGAQUFBzAC 30 | hjZodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xhc3MyLnNlcnZl 31 | ci5jYS5jcnQwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMA0G 32 | CSqGSIb3DQEBBQUAA4IBAQB+b3CkugXNnuO+1y8AThEIqJVOfdzV994DCYLOKMf7 33 | g1d4xztQUd/3FiZDQFhRQojeXSlqu8bk4SSIVI/LyhgtstlWIVvqsBKPEr4W12PN 34 | A3/IIhOIxt/oUJRfOsWKVpM4mhgWT2z7K5l0VWTpnD+NGU42Gqvxzfk6gu0gnhCJ 35 | yj2f5bOLcMHMoC8f1a6NlpwlKMWDjXt6VroWr/IA8ijNo4bsyR8UE257SCohikTX 36 | 24/vr8vT4mizPH0/PfTpq8e0iGbptYGgdRlQHRXJquYdl/ARPAmxI4BN0kM2+aNr 37 | ValY138U0Q7Rt8OllJyHFeIHk81HE6hBOP0sG43NzTsH 38 | -----END CERTIFICATE----- 39 | -------------------------------------------------------------------------------- /testdata/kuandcnnotinsan.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIIZjCCB06gAwIBAgIOAaR+hCzf1/0sHczKzAowDQYJKoZIhvcNAQELBQAweDEL 3 | MAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNyb3Nl 4 | YyBMdGQuMR0wGwYDVQQDDBRlLVN6aWdubyBTU0wgQ0EgMjAxNDEfMB0GCSqGSIb3 5 | DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0xNjAzMjExNDQyMzNaFw0xODAzMjEx 6 | NDQyMzNaMHoxCzAJBgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UE 7 | CgwNTWljcm9zZWMgTHRkLjEZMBcGA1UEAwwQd3d3LnBhc3NieW1lLmNvbTElMCMG 8 | A1UEBRMcMS4zLjYuMS40LjEuMjE1MjguMi4zLjIuMTY4MDCCASIwDQYJKoZIhvcN 9 | AQEBBQADggEPADCCAQoCggEBAOga9GbHfOG30z6TuWrqi91w8N0cTJECJG31S8Xz 10 | FJepL3o0hNipa9tqniEiczbnML1VeVyaqYlB01ZenXPGk5OzCgUw3XDWKlsxV2oV 11 | 2U8rZERRf+XEZm1QDYVXVo4fSYR7ScP2/GyBhcshIATOWwsms/D0ro+YaVPqJrh8 12 | fA/lB6QVGpOwfR+L4PixAM2j5yND+hTlBIoli4ofmq+XNzwSadXT8A0c2mylC/lV 13 | krHIj6aSo9lO6+jbNS3tB7a8ouSPv6lifHf69SWpWcqrxUj9TDSYDqx7MZ/++v6U 14 | eHA9cf+l+L0nwjDUvOwqIHHd4tPT9DDPvnJClO340u+quA0CAwEAAaOCBOowggTm 15 | MA4GA1UdDwEB/wQEAwIDqDATBgNVHSUEDDAKBggrBgEFBQcDATCCAikGA1UdIASC 16 | AiAwggIcMIICDgYOKwYBBAGBqBgCAQEiAwAwggH6MCYGCCsGAQUFBwIBFhpodHRw 17 | Oi8vY3AuZS1zemlnbm8uaHUvYWNwczCBkwYIKwYBBQUHAgIwgYYagYNOb24tcXVh 18 | bGlmaWVkIGNlcnRpZmljYXRlIG9mIGNsYXNzIDMuIElzc3VlZCB2aWEgZmFjZS10 19 | by1mYWNlIHJlZ2lzdHJhdGlvbi4gVGhlIHN1YmplY3Qgb2YgdGhlIGNlcnRpZmlj 20 | YXRlIGlzIG5vdCBhIG5hdHVyYWwgcGVyc29uLjCCATgGCCsGAQUFBwICMIIBKh6C 21 | ASYATgBlAG0AIABtAGkAbgFRAHMA7QB0AGUAdAB0ACAAdABhAG4A+gBzAO0AdAB2 22 | AOEAbgB5ACwAIAAzAC4AIABoAGkAdABlAGwAZQBzAO0AdADpAHMAaQAgAG8AcwB6 23 | AHQA4QBsAHkALAAgAHIAZQBnAGkAcwB6AHQAcgDhAGMAaQDzAGsAbwByACAAYQAg 24 | AHMAegBlAG0A6QBsAHkAZQBzACAAbQBlAGcAagBlAGwAZQBuAOkAcwAgAGsA9gB0 25 | AGUAbABlAHoBUQAuACAAQQAgAHQAYQBuAPoAcwDtAHQAdgDhAG4AeQAgAGEAbABh 26 | AG4AeQBhACAAbgBlAG0AIAB0AGUAcgBtAOkAcwB6AGUAdABlAHMAIABzAHoAZQBt 27 | AOkAbAB5AC4wCAYGBACPegEBMB0GA1UdDgQWBBRA8kM63oJ90fpVltCbapz7Q0cQ 28 | TTAfBgNVHSMEGDAWgBTearBOQ6oIQUd0v6WKgVRMIMV1KDBFBgNVHREEPjA8gg4q 29 | LnBhc3NieW1lLmNvbYIMcGFzc2J5bWUuY29tgg4qLnBhc3NieW1lLm5ldIIMcGFz 30 | c2J5bWUubmV0MIGwBgNVHR8EgagwgaUwNaAzoDGGL2h0dHA6Ly9zc2xjYTIwMTQt 31 | Y3JsMS5lLXN6aWduby5odS9zc2xjYTIwMTQuY3JsMDWgM6Axhi9odHRwOi8vc3Ns 32 | Y2EyMDE0LWNybDIuZS1zemlnbm8uaHUvc3NsY2EyMDE0LmNybDA1oDOgMYYvaHR0 33 | cDovL3NzbGNhMjAxNC1jcmwzLmUtc3ppZ25vLmh1L3NzbGNhMjAxNC5jcmwwggFW 34 | BggrBgEFBQcBAQSCAUgwggFEMC4GCCsGAQUFBzABhiJodHRwOi8vc3NsY2EyMDE0 35 | LW9jc3AxLmUtc3ppZ25vLmh1MC4GCCsGAQUFBzABhiJodHRwOi8vc3NsY2EyMDE0 36 | LW9jc3AyLmUtc3ppZ25vLmh1MC4GCCsGAQUFBzABhiJodHRwOi8vc3NsY2EyMDE0 37 | LW9jc3AzLmUtc3ppZ25vLmh1MDoGCCsGAQUFBzAChi5odHRwOi8vc3NsY2EyMDE0 38 | LWNhMS5lLXN6aWduby5odS9zc2xjYTIwMTQuY3J0MDoGCCsGAQUFBzAChi5odHRw 39 | Oi8vc3NsY2EyMDE0LWNhMi5lLXN6aWduby5odS9zc2xjYTIwMTQuY3J0MDoGCCsG 40 | AQUFBzAChi5odHRwOi8vc3NsY2EyMDE0LWNhMy5lLXN6aWduby5odS9zc2xjYTIw 41 | MTQuY3J0MA0GCSqGSIb3DQEBCwUAA4IBAQBInnMta3MKio/ZWcmivU17rrqGL8qx 42 | xeJTe9HcZRaX4xWVun+RitVO++f067L1nve+7JAQKwmt9/O4IX61saJMUJExvAg5 43 | Sfz+h59DWJNAkXUGvSCZRKTBXpE8ES8nqUuJBQ8FNPtjlCtwf4XHnPrMV3MRFDwH 44 | bT+8IcVZshv+kIvbHGh+4LV0kBFmKYDSD05RgY3Ne4sAk9sEUI+osKp8WgPMWhTy 45 | UoAxu1fpFd4iTf0tu15a2MnlGyf81LU6ptZfiOU59Af1vbDZbpL0XJQmvH10wR4q 46 | 8duAhKn65aqY7iQToJt7fxjlDBjluA0fRKR8xaqeyllART7kORI59dy6 47 | -----END CERTIFICATE----- 48 | -------------------------------------------------------------------------------- /examples/ct/ct.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/globalsign/certlint/asn1" 10 | "github.com/globalsign/certlint/certdata" 11 | "github.com/globalsign/certlint/checks" 12 | "github.com/globalsign/certlint/errors" 13 | 14 | _ "github.com/globalsign/certlint/checks/certificate/all" 15 | _ "github.com/globalsign/certlint/checks/extensions/all" 16 | 17 | ct "github.com/google/certificate-transparency-go" 18 | "github.com/google/certificate-transparency-go/client" 19 | "github.com/google/certificate-transparency-go/jsonclient" 20 | ) 21 | 22 | func main() { 23 | var logServer = flag.String("server", "https://ct.googleapis.com/aviator", "CT log server") 24 | var start = flag.Int64("start", 0, "CT log start index") 25 | flag.Parse() 26 | 27 | logClient, err := client.New(*logServer, nil, jsonclient.Options{}) 28 | if err != nil { 29 | fmt.Printf("Failed to create log client: %s\n", err.Error()) 30 | return 31 | } 32 | sth, err := logClient.GetSTH(context.Background()) 33 | if err != nil { 34 | fmt.Printf("Failed to get tree head: %s\n", err.Error()) 35 | return 36 | } 37 | 38 | var startIndex = *start 39 | for { 40 | entries, err := logClient.GetEntries(context.Background(), startIndex, startIndex+1000) 41 | if err != nil { 42 | fmt.Printf("Failed to get entries: %s\n", err.Error()) 43 | break 44 | } 45 | 46 | for _, entry := range entries { 47 | startIndex++ 48 | ctEntry(entry) 49 | } 50 | 51 | if !(startIndex < int64(sth.TreeSize)) { 52 | break 53 | } 54 | } 55 | } 56 | 57 | func ctEntry(entry ct.LogEntry) { 58 | switch entry.Leaf.TimestampedEntry.EntryType { 59 | case ct.X509LogEntryType: 60 | leaf, err := entry.Leaf.X509Certificate() 61 | if err != nil { 62 | fmt.Printf("Failed to get leaf certificate in entry %d: %s\n", entry.Index, err.Error()) 63 | return 64 | } 65 | if leaf != nil { 66 | check(leaf.Raw) 67 | } 68 | return 69 | 70 | case ct.PrecertLogEntryType: 71 | check(entry.Chain[0].Data) 72 | return 73 | 74 | default: 75 | fmt.Printf("Failed to parse unknown entry type: %s", entry.Leaf.TimestampedEntry.EntryType) 76 | return 77 | } 78 | } 79 | 80 | func check(der []byte) { 81 | var e = errors.New(nil) 82 | 83 | // Check the ASN1 structure for common formatting errors 84 | al := new(asn1.Linter) 85 | e.Append(al.CheckStruct(der)) 86 | 87 | // Load and parse certificate 88 | d, err := certdata.Load(der) 89 | if err == nil { 90 | // Don't check expired certificates 91 | if d.Cert.NotAfter.Before(time.Now()) { 92 | return 93 | } 94 | 95 | // Perform all and only the imported checks 96 | e.Append(checks.Certificate.Check(d)) 97 | } 98 | if d == nil { 99 | e.Err("Failed to load certificate") 100 | } 101 | 102 | // List all errors 103 | if e != nil { 104 | if d != nil { 105 | fmt.Printf("'%s' issued by '%s' (%s)\n", d.Cert.Subject.CommonName, d.Cert.Issuer.CommonName, d.Type) 106 | } 107 | for _, err := range e.List() { 108 | fmt.Printf("\t- %s\n", err.Error()) 109 | } 110 | fmt.Println() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /testdata/evissues2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIII8jCCBtqgAwIBAgIQHZTxDX3amNJXGIt5S4gjRjANBgkqhkiG9w0BAQsFADCB 3 | gTELMAkGA1UEBhMCRVMxFDASBgNVBAoMC0laRU5QRSBTLkEuMTgwNgYDVQQLDC9C 4 | WiBaaXVydGFnaXJpIHB1Ymxpa29hIC0gQ2VydGlmaWNhZG8gcHVibGljbyBFVjEi 5 | MCAGA1UEAwwZQ0EgZGUgQ2VydGlmaWNhZG9zIFNTTCBFVjAeFw0xNjA0MjEwODEy 6 | NDFaFw0xODA0MjEwODEyMzdaMIH0MRMwEQYLKwYBBAGCNzwCAQMTAkVTMRowGAYD 7 | VQQPDBFHb3Zlcm5tZW50IEVudGl0eTELMAkGA1UEBhMCRVMxNDAyBgNVBAoMK01B 8 | w5FBUklBS08gVURBTEEgLSBBWVVOVEFNSUVOVE8gREUgTUHDkUFSSUExGjAYBgNV 9 | BAsMEXNlZGUgZWxlY3Ryw7NuaWNhMTQwMgYDVQQLDCtNQcORQVJJQUtPIFVEQUxB 10 | IC0gQVlVTlRBTUlFTlRPIERFIE1Bw5FBUklBMRIwEAYDVQQFEwlQNDgwNzAwMEcx 11 | GDAWBgNVBAMMD3d3dy5tYW5hcmlhLmV1czCCASIwDQYJKoZIhvcNAQEBBQADggEP 12 | ADCCAQoCggEBALtkD54ltDSq2ztOD1QF72p7ROGXnaNMDG/cE8X2hCd+n8RiEL9+ 13 | 3ayfZoP7seqncGmrqRHgNs84CDCq34LO1DmLB1rAFgUAZnNDGMhTaMtM0zCht++K 14 | 05QPVyI0AMig4KcE/Vwl5SJqbXCxZykmaoswSkiELwewBO8qqJP+VszySw9E4WGW 15 | glZVY6am1olqZxK2oftrcFdSTdzGecibTUX+D7XoOGNwzSnbeC6eUhSot9B8Af+C 16 | DW9oZ5HHcRYdfPQ7vsKO1U0ucukTJaGw9FxXrH0PiXuXMuGTdtn08yH0EXe7cMnd 17 | TbOy6/L3JCt61dlsbyGEM5dx+Dy5GbuZreECAwEAAaOCA+8wggPrMIHHBgNVHRIE 18 | gb8wgbyGFWh0dHA6Ly93d3cuaXplbnBlLmNvbYEPaW5mb0BpemVucGUuY29tpIGR 19 | MIGOMUcwRQYDVQQKDD5JWkVOUEUgUy5BLiAtIENJRiBBMDEzMzcyNjAtUk1lcmMu 20 | Vml0b3JpYS1HYXN0ZWl6IFQxMDU1IEY2MiBTODFDMEEGA1UECQw6QXZkYSBkZWwg 21 | TWVkaXRlcnJhbmVvIEV0b3JiaWRlYSAxNCAtIDAxMDEwIFZpdG9yaWEtR2FzdGVp 22 | ejCCARMGA1UdEQSCAQowggEGgRl1ZGFsYS5tYW5hcmlhQGJpemthaWEub3JnpIHX 23 | MIHUMR4wHAYJYIVUAQMFAQIFDA93d3cubWFuYXJpYS5ldXMxOjA4BglghVQBAwUB 24 | AgQMK01Bw5FBUklBS08gVURBTEEgLSBBWVVOVEFNSUVOVE8gREUgTUHDkUFSSUEx 25 | GDAWBglghVQBAwUBAgMMCVA0ODA3MDAwRzE6MDgGCWCFVAEDBQECAgwrTUHDkUFS 26 | SUFLTyBVREFMQSAtIEFZVU5UQU1JRU5UTyBERSBNQcORQVJJQTEgMB4GCWCFVAED 27 | BQECAQwRc2VkZSBlbGVjdHLDs25pY2GCD3d3dy5tYW5hcmlhLmV1czATBgNVHSUE 28 | DDAKBggrBgEFBQcDATAdBgNVHQ4EFgQU7cSBOHYpYKHaqm6KSWcfVjOM23owHwYD 29 | VR0jBBgwFoAUps5paS6mITU7Os8K8S4/FawZkCcwggEeBgNVHSAEggEVMIIBETCC 30 | AQ0GCisGAQQB8zkGAQIwgf4wJQYIKwYBBQUHAgEWGWh0dHA6Ly93d3cuaXplbnBl 31 | LmNvbS9jcHMwgdQGCCsGAQUFBwICMIHHGoHEQmVybWVlbiBtdWdhayBlemFndXR6 32 | ZWtvIHd3dy5pemVucGUuY29tIFppdXJ0YWdpcmlhbiBrb25maWFudHphIGl6YW4g 33 | YXVycmV0aWsga29udHJhdHVhIGlyYWt1cnJpLkxpbWl0YWNpb25lcyBkZSBnYXJh 34 | bnRpYXMgZW4gd3d3Lml6ZW5wZS5jb20gQ29uc3VsdGUgZWwgY29udHJhdG8gYW50 35 | ZXMgZGUgY29uZmlhciBlbiBlbCBjZXJ0aWZpY2FkbzAyBggrBgEFBQcBAQQmMCQw 36 | IgYIKwYBBQUHMAGGFmh0dHA6Ly9vY3NwLml6ZW5wZS5jb20wDgYDVR0PAQH/BAQD 37 | AgWgMDgGA1UdHwQxMC8wLaAroCmGJ2h0dHA6Ly9jcmwuaXplbnBlLmNvbS9jZ2kt 38 | YmluL2NybHNzbGV2MjATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQsF 39 | AAOCAgEA2skneKstQesO4fmXiwzg/J6fenOVVEXbvVfpinSkPrvCGEILawsEzUGm 40 | vHhnMBkmCzjycdpCfsNuii38WVyAVYf1AOvbx8g5QXSGTCFdI7c8xWNH63TCsuxT 41 | kVbyxQNG4GAbgKSckhqGFhfEDb4VwVGJnE2pUzezmi2Lx7/e2SC9MIW+YK0FPH5k 42 | G/d/sQ9C7HYRbSVAK3hHMavi5MaksCoqaAa4izUR47sWOU5ihyJ1eAB1RYorSVEF 43 | V5KWRx+Ry7AfcX1lvSOyE6NheW0QkAqDpoo6Uo3gaN7vVCWkhcvx23lqQuk5Ne70 44 | Hc8yZHxHBkhKkYU3bDxYzccVwVvdJD0fh6KCzUu5ZrQ8MU0SGwsi1QPAn/6nP/l4 45 | tn2tBVyeWy8YGzGx+kRPGpFwDUy4i5EfABb7kExY9S1lUxwl3S/GCaBQQcD+E/ro 46 | JykRTuC8slDfvm38bhVbM/aakTDcZIUGoz9/RaFeT6SRo9gWztFcVrOSE4BoU+2L 47 | C6ec/BOtZPj+BT0XbcrlUX76OZZ8qPCMSncG6nPgBS3TawIA78giJzaS/baeKD0S 48 | 05ywP4SBTPSRcfLxCh2dGWteJDFTrmHg8PVJpvdsV+X1Z6yIBQqX+oygUPB0SWc7 49 | 4IKiW/nELlewUZyTPzuNHAvriA1n0Y1ZG8trUSbpy5gZtgirt7M= 50 | -----END CERTIFICATE----- 51 | -------------------------------------------------------------------------------- /certdata/type.go: -------------------------------------------------------------------------------- 1 | package certdata 2 | 3 | import ( 4 | "crypto/x509" 5 | "encoding/asn1" 6 | "fmt" 7 | "strings" 8 | 9 | psl "golang.org/x/net/publicsuffix" 10 | ) 11 | 12 | // setCertificateType set the base on how we check for other requirements of the 13 | // certificate. It's important that we reliably identify the purpose to apply 14 | // the right checks for that certificate type. 15 | func (d *Data) setCertificateType() error { 16 | // We want to be able to detect 'false' CA certificates, classify as CA 17 | // certificate is basic contains and key usage certsign are set. 18 | if d.Cert.IsCA && d.Cert.KeyUsage&x509.KeyUsageCertSign != 0 { 19 | d.Type = "CA" 20 | return nil 21 | } 22 | 23 | // The fallback type is used when a certificate could be any of a range 24 | // but further checks need to define the exact type. When these checks fail 25 | // the fallback type is used. 26 | var fallbackType string 27 | 28 | // Based on ExtKeyUsage 29 | for _, ku := range d.Cert.ExtKeyUsage { 30 | switch ku { 31 | case x509.ExtKeyUsageServerAuth: 32 | // Try to determine certificate type via policy oid 33 | d.Type = getType(d.Cert.PolicyIdentifiers) 34 | fallbackType = "DV" 35 | case x509.ExtKeyUsageClientAuth: 36 | fallbackType = "PS" 37 | case x509.ExtKeyUsageEmailProtection: 38 | d.Type = "PS" 39 | case x509.ExtKeyUsageCodeSigning: 40 | d.Type = "CS" 41 | case x509.ExtKeyUsageTimeStamping: 42 | d.Type = "TS" 43 | case x509.ExtKeyUsageOCSPSigning: 44 | d.Type = "OCSP" 45 | } 46 | } 47 | 48 | // If we have no known key usage, try the policy list again 49 | if d.Type == "" { 50 | d.Type = getType(d.Cert.PolicyIdentifiers) 51 | } 52 | 53 | // When determined by Policy Identifier we can stop 54 | if d.Type != "" { 55 | return nil 56 | } 57 | 58 | // Based on UnknownExtKeyUsage 59 | for _, ku := range d.Cert.UnknownExtKeyUsage { 60 | switch { 61 | case ku.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 21, 19}): 62 | // dsEmailReplication 63 | d.Type = "PS" 64 | return nil 65 | case ku.Equal(asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 8, 2, 2}): 66 | // IPSEC Protection 67 | d.Type = "IPSEC" 68 | return nil 69 | } 70 | } 71 | 72 | // Check if the e-mailAddress is set in the DN 73 | for _, n := range d.Cert.Subject.Names { 74 | switch { 75 | case n.Type.Equal(asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}): // e-mailAddress 76 | d.Type = "PS" 77 | return nil 78 | } 79 | } 80 | 81 | // An @ sing in the common name is often used in PS. 82 | if strings.Contains(d.Cert.Subject.CommonName, "@") { 83 | d.Type = "PS" 84 | return nil 85 | } else if strings.Contains(d.Cert.Subject.CommonName, " ") { 86 | d.Type = "PS" 87 | return nil 88 | } 89 | 90 | // If it's a fqdn, it's a EV, OV or DV 91 | if suffix, _ := psl.PublicSuffix(strings.ToLower(d.Cert.Subject.CommonName)); len(suffix) > 0 { 92 | if len(d.Cert.Subject.Organization) > 0 { 93 | if len(d.Cert.Subject.SerialNumber) > 0 { 94 | d.Type = "EV" 95 | return nil 96 | } 97 | 98 | d.Type = "OV" 99 | return nil 100 | } 101 | 102 | d.Type = "DV" 103 | return nil 104 | } 105 | 106 | if len(fallbackType) > 0 { 107 | d.Type = fallbackType 108 | return nil 109 | } 110 | 111 | if d.Type == "" { 112 | return fmt.Errorf("Could not determine certificate type") 113 | } 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /checks/certificate/subjectaltname/subjectaltname.go: -------------------------------------------------------------------------------- 1 | package subjectaltname 2 | 3 | import ( 4 | goerr "errors" 5 | "strings" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | 11 | "golang.org/x/net/idna" 12 | ) 13 | 14 | const checkName = "Subject Alternative Names Check" 15 | 16 | var idnaProfile *idna.Profile 17 | var idnaUnderscoreError = goerr.New("idna: disallowed rune U+005F") 18 | 19 | func init() { 20 | idnaProfile = idna.New( 21 | idna.BidiRule(), 22 | idna.MapForLookup(), 23 | idna.ValidateForRegistration(), 24 | idna.ValidateLabels(true), 25 | idna.VerifyDNSLength(true), 26 | idna.StrictDomainName(true), 27 | idna.Transitional(false)) 28 | 29 | checks.RegisterCertificateCheck(checkName, nil, Check) 30 | } 31 | 32 | // Check performs a strict verification on the extension according to the standard(s) 33 | func Check(d *certdata.Data) *errors.Errors { 34 | var e = errors.New(nil) 35 | 36 | // TODO: Should we check against cross usage of certificate types (for example DV certificate with email address)? 37 | 38 | switch d.Type { 39 | case "PS": 40 | // TODO: Check EmailAddresses in the Subject DN 41 | if len(d.Cert.EmailAddresses) == 0 { 42 | e.Err("Certificate doesn't contain any subjectAltName") 43 | return e 44 | } 45 | for _, s := range d.Cert.EmailAddresses { 46 | // Splitting domain of mail address, using lowercase 47 | em := strings.SplitAfter(strings.ToLower(s), "@") 48 | if len(em) != 2 { 49 | e.Err("Certificate subjectAltName '%s' contains an invalid email address", s) 50 | continue 51 | } 52 | 53 | // Check email address domain part 54 | if _, err := idnaProfile.ToASCII(em[1]); err != nil { 55 | e.Err("Certificate subjectAltName '%s', %s", s, err.Error()) 56 | } 57 | 58 | // TODO: Implement more checks for the left side of the @ sign 59 | if strings.Contains(em[0], " ") { 60 | e.Err("Certificate subjectAltName '%s' contains a whitespace", s) 61 | } 62 | } 63 | 64 | case "DV", "OV", "EV": 65 | if len(d.Cert.DNSNames) == 0 && len(d.Cert.IPAddresses) == 0 { 66 | e.Err("Certificate doesn't contain any subjectAltName") 67 | return e 68 | } 69 | 70 | // While the commonname is not a subjectAltName we use the same rule to 71 | // validate the domain name. Check with stripped wildcards as they are non 72 | // registrable. 73 | if _, err := idnaProfile.ToASCII(strings.TrimPrefix(strings.ToLower(d.Cert.Subject.CommonName), "*.")); err != nil && err != idnaUnderscoreError { 74 | e.Err("Certificate CommonName '%s', %s", d.Cert.Subject.CommonName, err.Error()) 75 | } 76 | 77 | var cnInSan bool 78 | for _, s := range d.Cert.DNSNames { 79 | if strings.EqualFold(d.Cert.Subject.CommonName, s) { 80 | cnInSan = true 81 | } 82 | 83 | // Check subjectAltName with stripped wildcards as they are non registrable 84 | if _, err := idnaProfile.ToASCII(strings.TrimPrefix(strings.ToLower(s), "*.")); err != nil && err != idnaUnderscoreError { 85 | e.Err("Certificate subjectAltName '%s', %s", s, err.Error()) 86 | } 87 | } 88 | 89 | // Maybe it's an IP address 90 | if !cnInSan { 91 | for _, s := range d.Cert.IPAddresses { 92 | if strings.EqualFold(d.Cert.Subject.CommonName, s.String()) { 93 | cnInSan = true 94 | } 95 | } 96 | } 97 | 98 | if !cnInSan { 99 | e.Err("Certificate CN is not listed in subjectAltName") 100 | } 101 | } 102 | 103 | return e 104 | } 105 | -------------------------------------------------------------------------------- /checks/extensions/ocspmuststaple/ocspmuststaple_test.go: -------------------------------------------------------------------------------- 1 | package ocspmuststaple 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | "testing" 6 | 7 | "github.com/globalsign/certlint/certdata" 8 | ) 9 | 10 | // TestCheck tests the OCSP Must Staple extension Check() behaves as expected 11 | // with valid/invalid testcases. 12 | func TestCheck(t *testing.T) { 13 | // Valid OCSP Must Staple Extension. 14 | validExtension := pkix.Extension{ 15 | Id: extensionOid, 16 | Value: expectedExtensionValue, 17 | Critical: false, 18 | } 19 | // Invalid OCSP Must Staple Extension: Critical field set to `true`. 20 | criticalExtension := pkix.Extension{ 21 | Id: extensionOid, 22 | Value: expectedExtensionValue, 23 | Critical: true, 24 | } 25 | // Invalid OCSP Must Staple Extension: Wrong value. 26 | wrongValueExtension := pkix.Extension{ 27 | Id: extensionOid, 28 | Value: []uint8{0xC0, 0xFF, 0xEE}, 29 | Critical: false, 30 | } 31 | // Invalid OCSP Must Staple Extension: Wrong value, Critical field set to 32 | // `true` 33 | wrongValueExtensionCritical := pkix.Extension{ 34 | Id: extensionOid, 35 | Value: []uint8{0xC0, 0xFF, 0xEE}, 36 | Critical: true, 37 | } 38 | 39 | testCases := []struct { 40 | Name string 41 | InputEx pkix.Extension 42 | CertType string 43 | ExpectedErrors []string 44 | }{ 45 | { 46 | Name: "Valid: DV cert type", 47 | InputEx: validExtension, 48 | CertType: "DV", 49 | ExpectedErrors: []string{}, 50 | }, 51 | { 52 | Name: "Valid: OV cert type", 53 | InputEx: validExtension, 54 | CertType: "DV", 55 | ExpectedErrors: []string{}, 56 | }, 57 | { 58 | Name: "Valid: EV cert type", 59 | InputEx: validExtension, 60 | CertType: "DV", 61 | ExpectedErrors: []string{}, 62 | }, 63 | { 64 | Name: "Valid: CA cert type", 65 | InputEx: validExtension, 66 | CertType: "CA", 67 | ExpectedErrors: []string{}, 68 | }, 69 | { 70 | Name: "Invalid: OCSP cert type", 71 | InputEx: validExtension, 72 | CertType: "OCSP", 73 | ExpectedErrors: []string{ 74 | certTypeErr, 75 | }, 76 | }, 77 | { 78 | Name: "Invalid: critical extension", 79 | InputEx: criticalExtension, 80 | CertType: "DV", 81 | ExpectedErrors: []string{critExtErr}, 82 | }, 83 | { 84 | Name: "Invalid: critical extension, OCSP cert type", 85 | InputEx: criticalExtension, 86 | CertType: "OCSP", 87 | ExpectedErrors: []string{ 88 | certTypeErr, critExtErr, 89 | }, 90 | }, 91 | { 92 | Name: "Invalid: wrong extension value", 93 | InputEx: wrongValueExtension, 94 | CertType: "DV", 95 | ExpectedErrors: []string{ 96 | extValueErr, 97 | }, 98 | }, 99 | { 100 | Name: "Invalid: wrong extension value, critical extension, OCSP cert type", 101 | InputEx: wrongValueExtensionCritical, 102 | CertType: "OCSP", 103 | ExpectedErrors: []string{ 104 | certTypeErr, critExtErr, extValueErr, 105 | }, 106 | }, 107 | } 108 | 109 | for _, tc := range testCases { 110 | t.Run(tc.Name, func(t *testing.T) { 111 | certData := &certdata.Data{ 112 | Type: tc.CertType, 113 | } 114 | // Run the OCSP Must Staple check on the test data 115 | errors := Check(tc.InputEx, certData) 116 | // Collect the returned errors into a list 117 | errList := errors.List() 118 | // Verify the expected number of errors are in the list 119 | if len(tc.ExpectedErrors) != len(errList) { 120 | t.Errorf("wrong number of Check errors: expected %d, got %d\n", 121 | len(tc.ExpectedErrors), len(errList)) 122 | } else { 123 | // Match the error list to the expected error list 124 | for i, err := range errList { 125 | if errMsg := err.Error(); errMsg != tc.ExpectedErrors[i] { 126 | t.Errorf("expected error %q at index %d, got %q", 127 | tc.ExpectedErrors[i], i, errMsg) 128 | } 129 | } 130 | } 131 | }) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # certlint 2 | 3 | [![Build Status](https://travis-ci.org/globalsign/certlint.svg?branch=master)](https://travis-ci.org/globalsign/certlint) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/globalsign/certlint)](https://goreportcard.com/report/github.com/globalsign/certlint) 5 | [![Coverage Status](http://codecov.io/github/globalsign/certlint/coverage.svg?branch=master)](http://codecov.io/github.com/globalsign/certlint?branch=master) 6 | [![GoDoc](https://godoc.org/github.com/globalsign/certlint?status.svg)](https://godoc.org/github.com/globalsign/certlint) 7 | 8 | X.509 certificate linter written in Go 9 | 10 | #### General 11 | This package is a work in progress. 12 | 13 | Please keep in mind that: 14 | - This is an early release and may contain bugs or false reports 15 | - Not all checks have been fully implemented or verified against the standard 16 | - CLI flag, APIs and CSV export are subject to change 17 | 18 | Code contributions and tests are highly welcome! 19 | 20 | #### Installation 21 | 22 | To install from source, just run: 23 | ```bash 24 | go get -u github.com/globalsign/certlint 25 | go install github.com/globalsign/certlint 26 | ``` 27 | 28 | #### CLI: Usage 29 | The 'certlint' command line utility included with this package can be used to test a single certificate or a large pem container to bulk test millions of certificates. The command is used to test the linter on a large number of certificates but could use fresh up to reduce code complexity. 30 | 31 | ``` 32 | Usage of ./certlint: 33 | -bulk string 34 | Bulk certificates file 35 | -cert string 36 | Certificate file 37 | -errlevel string 38 | Exit non-zero for Errors at this level (default "error") 39 | -expired 40 | Test expired certificates 41 | -help 42 | Show this help 43 | -include 44 | Include certificates in report 45 | -issuer string 46 | Certificate file 47 | -pprof 48 | Generate pprof profile 49 | -report string 50 | Report filename (default "report.csv") 51 | -revoked 52 | Check if certificates are revoked 53 | ``` 54 | 55 | ##### CLI: One certificate 56 | ```bash 57 | $ certlint -cert certificate.pem 58 | ``` 59 | 60 | ##### CLI: One certificate, exiting non-zero for Warning and above 61 | ```bash 62 | $ certlint -errlevel warning -cert certificate.pem 63 | ``` 64 | 65 | ##### CLI: A series of PEM encoded certificates 66 | ```bash 67 | $ certlint -bulk largestore.pem 68 | ``` 69 | 70 | ##### CLI: Testing expired certificates 71 | ```bash 72 | $ certlint -expired -bulk largestore.pem 73 | ``` 74 | 75 | ##### API: Usage 76 | Import one or all of these packages: 77 | 78 | ```go 79 | import "github.com/globalsign/certlint/asn1" 80 | import "github.com/globalsign/certlint/certdata" 81 | import "github.com/globalsign/certlint/checks" 82 | ``` 83 | 84 | You can import all available checks: 85 | ```go 86 | _ "github.com/globalsign/certlint/checks/extensions/all" 87 | _ "github.com/globalsign/certlint/checks/certificate/all" 88 | ``` 89 | 90 | Or you can just import a restricted set: 91 | ```go 92 | // Check for certificate (ext) KeyUsage extension 93 | _ "github.com/globalsign/certlint/checks/extensions/extkeyusage" 94 | _ "github.com/globalsign/certlint/checks/extensions/keyusage" 95 | 96 | // Also check the parsed certificate (ext) keyusage content 97 | _ "github.com/globalsign/certlint/checks/certificate/extkeyusage" 98 | _ "github.com/globalsign/certlint/checks/certificate/keyusage" 99 | ``` 100 | 101 | ##### API: Check ASN.1 value formatting 102 | ```go 103 | al := new(asn1.Linter) 104 | e := al.CheckStruct(der) 105 | if e != nil { 106 | for _, err := range e.List() { 107 | fmt.Println(err) 108 | } 109 | } 110 | ``` 111 | 112 | ##### API: Check certificate details 113 | ```go 114 | d, err := certdata.Load(der) 115 | if err == nil { 116 | e := checks.Certificate.Check(d) 117 | if e != nil { 118 | for _, err := range e.List() { 119 | fmt.Println(err) 120 | } 121 | } 122 | } 123 | ``` 124 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | //go:generate stringer -type=Priority 9 | 10 | // Priority defines how an error should be threaded 11 | type Priority int 12 | 13 | // Priorities that can be used to create and list errors 14 | const ( 15 | Unknown Priority = iota 16 | Debug 17 | Info 18 | Notice 19 | Warning 20 | Error 21 | Critical 22 | Alert 23 | Emergency 24 | ) 25 | 26 | // Config defines the error configuration, currently no configuration options 27 | // are available. 28 | type Config struct { 29 | } 30 | 31 | // Err contains a single error 32 | type Err struct { 33 | p Priority 34 | msg string 35 | } 36 | 37 | // Priority returns the priority of this error 38 | func (e Err) Priority() Priority { 39 | return e.p 40 | } 41 | 42 | // String returns the message of this error 43 | func (e Err) Error() string { 44 | return e.msg 45 | } 46 | 47 | // Errors contains a list of Error 48 | type Errors struct { 49 | err []Err 50 | config *Config 51 | p Priority 52 | m sync.Mutex 53 | } 54 | 55 | // New creates an empty error container 56 | func New(c *Config) *Errors { 57 | return &Errors{config: c} 58 | } 59 | 60 | // IsError returns true on one or more errors 61 | func (e *Errors) IsError() bool { 62 | if len(e.err) > 0 { 63 | return true 64 | } 65 | return false 66 | } 67 | 68 | // Priority returns the priority of this error 69 | func (e *Errors) Priority() Priority { 70 | return e.p 71 | } 72 | 73 | // List returns all errors of a given priority, if no priority is given all 74 | // errors are returned. 75 | func (e *Errors) List(p ...Priority) []Err { 76 | if len(p) == 0 { 77 | return e.err 78 | } 79 | 80 | // filter errors to given priorities 81 | var l []Err 82 | for _, e := range e.err { 83 | for _, priority := range p { 84 | if e.p == priority { 85 | l = append(l, e) 86 | } 87 | } 88 | } 89 | return l 90 | } 91 | 92 | // Append add all Errors to existing Errors 93 | func (e *Errors) Append(err *Errors) error { 94 | if err == nil { 95 | return nil 96 | } 97 | 98 | e.m.Lock() 99 | 100 | // append Errors at the end of the current list 101 | e.err = append(e.err, err.err...) 102 | 103 | // set highest priority 104 | if err.p > e.p { 105 | e.p = err.p 106 | } 107 | 108 | e.m.Unlock() 109 | return nil 110 | } 111 | 112 | // Emerg log an error with severity Emergency 113 | func (e *Errors) Emerg(format string, a ...interface{}) error { 114 | return e.add(Emergency, format, a...) 115 | } 116 | 117 | // Alert log an error with severity Alert 118 | func (e *Errors) Alert(format string, a ...interface{}) error { 119 | return e.add(Alert, format, a...) 120 | } 121 | 122 | // Crit log an error with severity Critical 123 | func (e *Errors) Crit(format string, a ...interface{}) error { 124 | return e.add(Critical, format, a...) 125 | } 126 | 127 | // Err log an error with severity Error 128 | func (e *Errors) Err(format string, a ...interface{}) error { 129 | return e.add(Error, format, a...) 130 | } 131 | 132 | // Warning log an error with severity Warning 133 | func (e *Errors) Warning(format string, a ...interface{}) error { 134 | return e.add(Warning, format, a...) 135 | } 136 | 137 | // Notice log an error with severity Notice 138 | func (e *Errors) Notice(format string, a ...interface{}) error { 139 | return e.add(Notice, format, a...) 140 | } 141 | 142 | // Info log an error with severity Info 143 | func (e *Errors) Info(format string, a ...interface{}) error { 144 | return e.add(Info, format, a...) 145 | } 146 | 147 | // Debug log an error with severity Debug 148 | func (e *Errors) Debug(format string, a ...interface{}) error { 149 | return e.add(Debug, format, a...) 150 | } 151 | 152 | func (e *Errors) add(p Priority, format string, a ...interface{}) error { 153 | // no error in request 154 | if len(format) == 0 && len(a) == 0 { 155 | return nil 156 | } 157 | 158 | e.m.Lock() 159 | 160 | msg := format 161 | if len(a) > 0 { 162 | msg = fmt.Sprintf(format, a...) 163 | } 164 | 165 | // add this priority to the end of the list 166 | e.err = append(e.err, Err{ 167 | p: p, 168 | msg: msg, 169 | }) 170 | 171 | // set highest priority in this list 172 | if p > e.p { 173 | e.p = p 174 | } 175 | 176 | e.m.Unlock() 177 | return nil 178 | } 179 | -------------------------------------------------------------------------------- /checks/certificate/subject/oid.go: -------------------------------------------------------------------------------- 1 | package subject 2 | 3 | import ( 4 | "encoding/asn1" 5 | "fmt" 6 | ) 7 | 8 | // Max field length: 9 | // https://www.itu.int/ITU-T/formal-language/itu-t/x/x520/2001/UpperBounds.html 10 | type object struct { 11 | oid asn1.ObjectIdentifier 12 | maxLength int 13 | } 14 | 15 | func (o *object) Equal(oid asn1.ObjectIdentifier) bool { 16 | return o.oid.Equal(oid) 17 | } 18 | 19 | func (o *object) Valid(v interface{}) error { 20 | if o.maxLength > 0 && len([]rune(v.(string))) > o.maxLength { 21 | return fmt.Errorf("exceeding max length of %d", o.maxLength) 22 | } 23 | return nil 24 | } 25 | 26 | // http://www.alvestrand.no/objectid/2.5.4.html 27 | var ( 28 | objectClass = object{asn1.ObjectIdentifier{2, 5, 4, 0}, 0} 29 | aliasedEntryName = object{asn1.ObjectIdentifier{2, 5, 4, 1}, 0} 30 | knowldgeinformation = object{asn1.ObjectIdentifier{2, 5, 4, 2}, 0} 31 | commonName = object{asn1.ObjectIdentifier{2, 5, 4, 3}, 64} 32 | surname = object{asn1.ObjectIdentifier{2, 5, 4, 4}, 40} 33 | serialNumber = object{asn1.ObjectIdentifier{2, 5, 4, 5}, 64} 34 | countryName = object{asn1.ObjectIdentifier{2, 5, 4, 6}, 2} 35 | localityName = object{asn1.ObjectIdentifier{2, 5, 4, 7}, 128} 36 | stateOrProvinceName = object{asn1.ObjectIdentifier{2, 5, 4, 8}, 128} 37 | streetAddress = object{asn1.ObjectIdentifier{2, 5, 4, 9}, 128} 38 | organizationName = object{asn1.ObjectIdentifier{2, 5, 4, 10}, 64} 39 | organizationalUnitName = object{asn1.ObjectIdentifier{2, 5, 4, 11}, 64} 40 | title = object{asn1.ObjectIdentifier{2, 5, 4, 12}, 64} 41 | description = object{asn1.ObjectIdentifier{2, 5, 4, 13}, 1024} 42 | searchGuide = object{asn1.ObjectIdentifier{2, 5, 4, 14}, 32768} 43 | businessCategory = object{asn1.ObjectIdentifier{2, 5, 4, 15}, 128} 44 | postalAddress = object{asn1.ObjectIdentifier{2, 5, 4, 16}, 128} 45 | postalCode = object{asn1.ObjectIdentifier{2, 5, 4, 17}, 40} 46 | postOfficeBox = object{asn1.ObjectIdentifier{2, 5, 4, 18}, 40} 47 | physicalDeliveryOfficeName = object{asn1.ObjectIdentifier{2, 5, 4, 19}, 128} 48 | telephoneNumber = object{asn1.ObjectIdentifier{2, 5, 4, 20}, 32} 49 | telexNumber = object{asn1.ObjectIdentifier{2, 5, 4, 21}, 14} 50 | teletexTerminalIdentifier = object{asn1.ObjectIdentifier{2, 5, 4, 22}, 1024} 51 | facsimileTelephoneNumber = object{asn1.ObjectIdentifier{2, 5, 4, 23}, 32} 52 | x121Address = object{asn1.ObjectIdentifier{2, 5, 4, 24}, 15} 53 | internationalISDNNumber = object{asn1.ObjectIdentifier{2, 5, 4, 25}, 16} 54 | registeredAddress = object{asn1.ObjectIdentifier{2, 5, 4, 26}, 128} 55 | destinationIndicator = object{asn1.ObjectIdentifier{2, 5, 4, 27}, 128} 56 | preferredDeliveryMethod = object{asn1.ObjectIdentifier{2, 5, 4, 28}, 0} 57 | presentationAddress = object{asn1.ObjectIdentifier{2, 5, 4, 29}, 0} 58 | supportedApplicationContext = object{asn1.ObjectIdentifier{2, 5, 4, 30}, 0} 59 | member = object{asn1.ObjectIdentifier{2, 5, 4, 31}, 0} 60 | owner = object{asn1.ObjectIdentifier{2, 5, 4, 32}, 0} 61 | roleOccupant = object{asn1.ObjectIdentifier{2, 5, 4, 33}, 0} 62 | seeAlso = object{asn1.ObjectIdentifier{2, 5, 4, 34}, 0} 63 | userPassword = object{asn1.ObjectIdentifier{2, 5, 4, 35}, 128} 64 | userCertificate = object{asn1.ObjectIdentifier{2, 5, 4, 36}, 0} 65 | cACertificate = object{asn1.ObjectIdentifier{2, 5, 4, 37}, 0} 66 | authorityRevocationList = object{asn1.ObjectIdentifier{2, 5, 4, 38}, 0} 67 | certificateRevocationList = object{asn1.ObjectIdentifier{2, 5, 4, 39}, 0} 68 | crossCertificatePair = object{asn1.ObjectIdentifier{2, 5, 4, 40}, 0} 69 | name = object{asn1.ObjectIdentifier{2, 5, 4, 41}, 128} 70 | givenName = object{asn1.ObjectIdentifier{2, 5, 4, 42}, 128} 71 | initials = object{asn1.ObjectIdentifier{2, 5, 4, 43}, 0} 72 | generationQualifier = object{asn1.ObjectIdentifier{2, 5, 4, 44}, 0} 73 | uniqueIdentifier = object{asn1.ObjectIdentifier{2, 5, 4, 45}, 0} 74 | dnQualifier = object{asn1.ObjectIdentifier{2, 5, 4, 46}, 0} 75 | enhancedSearchGuide = object{asn1.ObjectIdentifier{2, 5, 4, 47}, 0} 76 | protocolInformation = object{asn1.ObjectIdentifier{2, 5, 4, 48}, 0} 77 | distinguishedName = object{asn1.ObjectIdentifier{2, 5, 4, 49}, 0} 78 | uniqueMember = object{asn1.ObjectIdentifier{2, 5, 4, 50}, 0} 79 | houseIdentifier = object{asn1.ObjectIdentifier{2, 5, 4, 51}, 0} 80 | supportedAlgorithms = object{asn1.ObjectIdentifier{2, 5, 4, 52}, 0} 81 | deltaRevocationList = object{asn1.ObjectIdentifier{2, 5, 4, 53}, 0} 82 | attributeCertificate = object{asn1.ObjectIdentifier{2, 5, 4, 58}, 0} 83 | pseudonym = object{asn1.ObjectIdentifier{2, 5, 4, 65}, 0} 84 | 85 | emailAddress = object{asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}, 255} 86 | 87 | jurisdictionLocalityName = object{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 60, 2, 1, 1}, 128} 88 | jurisdictionStateOrProvinceName = object{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 60, 2, 1, 2}, 128} 89 | jurisdictionCountryName = object{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 60, 2, 1, 3}, 2} 90 | ) 91 | -------------------------------------------------------------------------------- /certdata/policyoid.go: -------------------------------------------------------------------------------- 1 | package certdata 2 | 3 | import "encoding/asn1" 4 | 5 | // Source GlobalSign CP and Cabforum BR 6 | // https://www.globalsign.com/en/repository/GlobalSign_CP_v5.3.pdf 7 | var polOidType []oidType 8 | 9 | type oidType struct { 10 | ObjectIdentifier asn1.ObjectIdentifier 11 | Type string 12 | } 13 | 14 | func getType(oid []asn1.ObjectIdentifier) string { 15 | for _, poid := range oid { 16 | for _, oidt := range polOidType { 17 | if poid.Equal(oidt.ObjectIdentifier) { 18 | return oidt.Type 19 | } 20 | } 21 | } 22 | return "" 23 | } 24 | 25 | // TODO: Can we handle this differently, we might want to use a constant here? 26 | func init() { 27 | // Extended Validation 28 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 1}, "EV"}) // Extended Validation Certificates Policy – SSL 29 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 2}, "CS"}) // Extended Validation Certificates Policy – Code Signing 30 | 31 | // Domain Validation 32 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 10}, "DV"}) // Domain Validation Certificates Policy 33 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 10, 10}, "DV"}) // Domain Validation Certificates Policy – AlphaSSL 34 | 35 | // Organization Validation 36 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 20}, "OV"}) // Organization Validation Certificates Policy 37 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 21}, "-"}) // Untrusted OneClickSSL Test Certificate (not in cp) 38 | 39 | // Intranet Validation 40 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 25}, "IN"}) // IntranetSSL Validation Certificates Policy 41 | 42 | // Time Stamping 43 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 30}, "TS"}) // Time Stamping Certificates Policy 44 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 31}, "TS"}) // Time Stamping Certificates Policy – AATL 45 | 46 | // Client Certificates 47 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40}, "PS"}) // Client Certificates Policy (Generic) 48 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40, 10}, "PS"}) // Client Certificates Policy (ePKI – Enterprise PKI) 49 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40, 20}, "PS"}) // Client Certificates Policy (JCAN – Japan CA Network) 50 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40, 30}, "PS"}) // Client Certificates Policy (AATL) 51 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40, 40}, "PS"}) // Client Certificates Policy (ePKI for private CAs) 52 | 53 | // Code Signing 54 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 50}, "CS"}) // Code Signing Certificates Policy 55 | 56 | // CA Chaining and Cross Signing 57 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 60}, "CA"}) // CA Chaining Policy – Trusted Root and Hosted Root 58 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 60, 1}, "CA"}) // CA Chaining Policy – Trusted Root (Baseline Requirements Compatible) 59 | 60 | // Others 61 | /*polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,80}, "XX"}) // Retail Industry Electronic Data Interchange Client Certificate Policy 62 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,81}, "XX"}) // Retail Industry Electronic Data Interchange Server Certificate Policy 63 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,90}, "XX"}) // Trusted Root TPM Policy 64 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,95}, "XX"}) // Online Certificate Status Protocol Policy 65 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,70}, "XX"}) // High Volume CA Policy 66 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1,3,6,1,4,1,4146,1,26}, "XX"}) // Test Certificate Policy (Should not be trusted) 67 | 68 | // In addition to these identifiers, all Certificates that comply with the NAESB Business 69 | // Practice Standards will include one of the following additional identifiers:- 70 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2,16,840,1,114505,1,12,1,2}, "XX"}) // NAESB Rudimentary Assurance 71 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2,16,840,1,114505,1,12,2,2}, "XX"}) // NAESB Basic Assurance 72 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2,16,840,1,114505,1,12,3,2}, "XX"}) // NAESB Medium Assurance 73 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2,16,840,1,114505,1,12,4,2}, "XX"}) // NAESB High Assurance 74 | */ 75 | // In addition to these identifiers, all Certificates that comply with the Baseline 76 | // Requirements will include the following additional identifiers:- 77 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 1}, "EV"}) // Extended Validation Certificate Policy 78 | //polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2,23,140,1,2}, ""}) // BR Compliance Certificate Policy 79 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 3}, "EVCS"}) // Extended Validation Code Signing Certificates Policy 80 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 4}, "CS"}) // BR Compliance Code Signing Certificates Policy 81 | 82 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 2, 1}, "DV"}) // Domain Validation Certificates Policy 83 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 2, 2}, "OV"}) // Organization Validation Certificates Policy 84 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{2, 23, 140, 1, 2, 3}, "IV"}) // Individual Validation Certificates Policy 85 | 86 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 2, 1}, "PS"}) // Adobe Certificate Policy Attribute Object Identifier (PDF) 87 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 2, 840, 113583, 1, 2, 2}, "PS"}) // Test Adobe Certificate Policy Attribute Object Identifier 88 | 89 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 4146, 1, 40, 30, 2}, "PS"}) // AATL Adobe Certificate Policy Attribute Object Identifier 90 | polOidType = append(polOidType, oidType{asn1.ObjectIdentifier{1, 2, 392, 200063, 30, 5300}, "PS"}) // JCAN 91 | } 92 | -------------------------------------------------------------------------------- /checks/certificate/subject/subject.go: -------------------------------------------------------------------------------- 1 | package subject 2 | 3 | import ( 4 | "crypto/x509/pkix" 5 | 6 | "github.com/globalsign/certlint/certdata" 7 | "github.com/globalsign/certlint/checks" 8 | "github.com/globalsign/certlint/errors" 9 | ) 10 | 11 | const checkName = "Subject Check" 12 | 13 | func init() { 14 | filter := &checks.Filter{ 15 | //Type: []string{"DV", "OV", "IV", "EV"}, 16 | } 17 | checks.RegisterCertificateCheck(checkName, filter, Check) 18 | } 19 | 20 | // Check performs a strict verification on the extension according to the standard(s) 21 | func Check(d *certdata.Data) *errors.Errors { 22 | return checkDN(d.Type, d.Cert.Subject.Names) 23 | } 24 | 25 | // Subject Distinguished Name Fields 26 | func checkDN(vetting string, dn []pkix.AttributeTypeAndValue) *errors.Errors { 27 | var e = errors.New(nil) 28 | 29 | // DNS must not be empty 30 | if len(dn) == 0 { 31 | e.Err("Distinguished Name contains no values") 32 | return e 33 | } 34 | 35 | // OV & EV requirements 36 | if vetting == "OV" || vetting == "EV" { 37 | if !inDN(dn, organizationName) { 38 | e.Err("organizationName is required for %s certificates", vetting) 39 | } 40 | } 41 | 42 | // EV specific requirements 43 | if vetting == "EV" { 44 | if !inDN(dn, localityName) { 45 | e.Err("localityName is required for %s certificates", vetting) 46 | } 47 | if !inDN(dn, businessCategory) { 48 | e.Err("businessCategory is required for %s certificates", vetting) 49 | } 50 | if !inDN(dn, jurisdictionCountryName) { 51 | e.Err("jurisdictionCountryName is required for %s certificates", vetting) 52 | } 53 | if !inDN(dn, serialNumber) { 54 | e.Err("serialNumber is required for %s certificates", vetting) 55 | } 56 | } 57 | 58 | // Field related requirements 59 | // 60 | // Max field length: 61 | // https://www.itu.int/ITU-T/formal-language/itu-t/x/x520/2001/UpperBounds.html 62 | for _, n := range dn { 63 | switch { 64 | 65 | // commonName 66 | // If present, this field MUST contain a single IP address or Fully‐Qualified Domain Name 67 | case commonName.Equal(n.Type): 68 | // report deprecated common name field as info until not commonly used/accepted 69 | e.Info("commonName field is deprecated") 70 | 71 | // check if value is exceeding max length 72 | if err := commonName.Valid(n.Value); err != nil { 73 | e.Err("commonName %s", err.Error()) 74 | } 75 | 76 | case emailAddress.Equal(n.Type): 77 | // report deprecated email address field as info until not commonly used/accepted 78 | e.Info("emailAddress field is deprecated") 79 | 80 | // RFC5280: ub-emailaddress-length was changed from 128 to 255 in order to 81 | // align with PKCS #9 [RFC2985]. 82 | if err := emailAddress.Valid(n.Value); err != nil { 83 | e.Err("emailAddress %s", err.Error()) 84 | } 85 | 86 | // surname 87 | // A Certificate containing a givenName field or surname field MUST contain 88 | // the (2.23.140.1.2.3) Certificate Policy OID. 89 | case surname.Equal(n.Type): 90 | // Prohibited 91 | if !inDN(dn, givenName) { 92 | e.Err("surname may only set in combination with givenName") 93 | } 94 | // Require field if surname is set 95 | if !inDN(dn, localityName) && !inDN(dn, stateOrProvinceName) { 96 | e.Err("localityName or stateOrProvinceName is required if surname is set") 97 | } 98 | 99 | // ub-surname-length INTEGER ::= 40 100 | if err := surname.Valid(n.Value); err != nil { 101 | e.Err("surname %s", err.Error()) 102 | } 103 | 104 | // countryName 105 | case countryName.Equal(n.Type): 106 | // TODO: Check against the values in ISO 3166‐1 107 | if len(n.Value.(string)) != 2 { 108 | e.Err("countryName MUST contain the two-letter ISO 3166-1 country code") 109 | } 110 | 111 | // jurisdictionCountryName 112 | case jurisdictionCountryName.Equal(n.Type): 113 | // TODO: Check against the values in ISO 3166‐1 114 | if len(n.Value.(string)) != 2 { 115 | e.Err("jurisdictionCountryName MUST contain the two-letter ISO 3166-1 country code") 116 | } 117 | 118 | // localityName 119 | case localityName.Equal(n.Type): 120 | // Prohibited 121 | if !inDN(dn, organizationName) && !(inDN(dn, givenName) && inDN(dn, surname)) { 122 | e.Err("localityName is not allowed without organizationName or givenName and surname") 123 | } 124 | 125 | if err := localityName.Valid(n.Value); err != nil { 126 | e.Err("localityName %s", err.Error()) 127 | } 128 | 129 | // stateOrProvinceName 130 | case stateOrProvinceName.Equal(n.Type): 131 | // Prohibited 132 | if !inDN(dn, organizationName) && !(inDN(dn, givenName) && inDN(dn, surname)) { 133 | e.Err("stateOrProvinceName is not allowed without organizationName or givenName and surname") 134 | } 135 | 136 | if err := stateOrProvinceName.Valid(n.Value); err != nil { 137 | e.Err("stateOrProvinceName %s", err.Error()) 138 | } 139 | 140 | // streetAddress 141 | case streetAddress.Equal(n.Type): 142 | // Prohibited 143 | if !inDN(dn, organizationName) && !(inDN(dn, givenName) && inDN(dn, surname)) { 144 | e.Err("streetAddress is not allowed without organizationName or givenName and surname") 145 | } 146 | 147 | if err := streetAddress.Valid(n.Value); err != nil { 148 | e.Err("streetAddress %s", err.Error()) 149 | } 150 | 151 | // postalCode 152 | case postalCode.Equal(n.Type): 153 | // Prohibited 154 | if !inDN(dn, organizationName) && !(inDN(dn, givenName) && inDN(dn, surname)) { 155 | e.Err("postalCode is not allowed without organizationName or givenName and surname") 156 | } 157 | 158 | if err := postalCode.Valid(n.Value); err != nil { 159 | e.Err("postalCode %s", err.Error()) 160 | } 161 | 162 | // organizationName 163 | case organizationName.Equal(n.Type): 164 | // Require field if organizationName is set 165 | if !inDN(dn, localityName) && !inDN(dn, stateOrProvinceName) { 166 | e.Err("localityName or stateOrProvinceName is required if organizationName is set") 167 | } 168 | if !inDN(dn, countryName) { 169 | e.Err("countryName is required if organizationName is set") 170 | } 171 | 172 | if err := organizationName.Valid(n.Value); err != nil { 173 | e.Err("organizationName %s", err.Error()) 174 | } 175 | 176 | // organizationalUnitName 177 | case organizationalUnitName.Equal(n.Type): 178 | if err := organizationalUnitName.Valid(n.Value); err != nil { 179 | e.Err("organizationalUnitName %s", err.Error()) 180 | } 181 | 182 | // businessCategory 183 | case businessCategory.Equal(n.Type): 184 | bc := n.Value.(string) 185 | if bc != "Private Organization" && bc != "Government Entity" && bc != "Business Entity" && bc != "Non-Commercial Entity" { 186 | e.Err("businessCategory should contain 'Private Organization', 'Government Entity', 'Business Entity', or 'Non-Commercial Entity'") 187 | } 188 | 189 | if err := businessCategory.Valid(n.Value); err != nil { 190 | e.Err("businessCategory %s", err.Error()) 191 | } 192 | 193 | // serialNumber 194 | case serialNumber.Equal(n.Type): 195 | if err := serialNumber.Valid(n.Value); err != nil { 196 | e.Err("serialNumber %s", err.Error()) 197 | } 198 | 199 | // givenName 200 | case givenName.Equal(n.Type): 201 | // Prohibited 202 | if !inDN(dn, surname) { 203 | e.Err("givenName may only set in combination with surname") 204 | } 205 | 206 | if err := givenName.Valid(n.Value); err != nil { 207 | e.Err("givenName %s", err.Error()) 208 | } 209 | } 210 | } 211 | 212 | return e 213 | } 214 | 215 | func inDN(dn []pkix.AttributeTypeAndValue, attr object) bool { 216 | for _, n := range dn { 217 | if attr.Equal(n.Type) { 218 | return true 219 | } 220 | } 221 | return false 222 | } 223 | -------------------------------------------------------------------------------- /asn1/format.go: -------------------------------------------------------------------------------- 1 | package asn1 2 | 3 | import ( 4 | "bytes" 5 | "encoding/asn1" 6 | "regexp" 7 | "strings" 8 | "time" 9 | "unicode" 10 | "unicode/utf8" 11 | ) 12 | 13 | // RFC 5280 4.1.2.5.1: UTCTime MUST include seconds, even when 00 14 | var formatUTCTime = regexp.MustCompile("^([0-9]{2})([01][0-9])([0-3][0-9])([012][0-9])([0-5][0-9]){2}Z$") 15 | var formatGeneralizedTime = regexp.MustCompile("^([0-9]{4})([01][0-9])([0-3][0-9])([012][0-9])([0-5][0-9]){2}Z$") 16 | 17 | // CheckFormat returns a list of formatting errors based on the expected ASN1 18 | // encoding according to the class and tag of the raw value. 19 | // TODO: Create checks for remaining class 0 tags 20 | // TODO: Should we create extensions for other classes, even include class 0? 21 | func (l *Linter) CheckFormat(d asn1.RawValue) { 22 | if d.Class == 0 { 23 | switch d.Tag { 24 | case 0: // "reserved for BER" 25 | case 1: // "BOOLEAN" 26 | case 2: // "INTEGER" 27 | case 3: // "BIT STRING" 28 | case 4: // "OCTET STRING" 29 | case 5: // "NULL" 30 | case 6: // "OBJECT IDENTIFIER" 31 | case 7: // "ObjectDescriptor" 32 | case 8: // "INSTANCE OF, EXTERNAL" 33 | case 9: // "REAL" 34 | case 10: // "ENUMERATED" 35 | case 11: // "EMBEDDED PDV" 36 | case 12: // "UTF8String" 37 | if !utf8.Valid(d.Bytes) { 38 | l.e.Err("Invalid UTF8 encoding in UTF8String") 39 | } 40 | if isForbiddenString(d.Bytes) { 41 | l.e.Err("Forbidden value in UTF8String '%s'", string(d.Bytes)) 42 | } 43 | if isControlCharacter(d.Bytes) { 44 | l.e.Err("Control character in UTF8String '%s'", string(d.Bytes)) 45 | } 46 | case 13: // "RELATIVE-OID" 47 | case 16: // "SEQUENCE, SEQUENCE OF" 48 | case 17: // "SET, SET OF" 49 | case 18: // "NumericString" 50 | if !isNumericString(d.Bytes) { 51 | l.e.Err("Invalid character in NumericString '%s'", string(d.Bytes)) 52 | } 53 | case 19: // "PrintableString" 54 | for _, b := range d.Bytes { 55 | if !isPrintable(b) { 56 | l.e.Err("Invalid character in PrintableString '%s'", string(d.Bytes)) 57 | } 58 | } 59 | if isForbiddenString(d.Bytes) { 60 | l.e.Err("Forbidden value in PrintableString '%s'", string(d.Bytes)) 61 | } 62 | case 20: // "TeletexString, T61String" 63 | l.e.Warning("Using deprecated TeletexString for '%s'", string(d.Bytes)) 64 | if isForbiddenString(d.Bytes) { 65 | l.e.Err("Forbidden value in TeletexString '%s'", string(d.Bytes)) 66 | } 67 | if isControlCharacter(d.Bytes) { 68 | l.e.Err("Control character in TeletexString '%s'", string(d.Bytes)) 69 | } 70 | case 21: // "VideotexString" 71 | l.e.Warning("Using deprecated VideotexString for '%s'", string(d.Bytes)) 72 | if isForbiddenString(d.Bytes) { 73 | l.e.Err("Forbidden value in VideotexString '%s'", string(d.Bytes)) 74 | } 75 | if isControlCharacter(d.Bytes) { 76 | l.e.Err("Control character in VideotexString '%s'", string(d.Bytes)) 77 | } 78 | case 22: // "IA5String" 79 | if !isIA5String(d.Bytes) { 80 | l.e.Err("Invalid character in IA5String '%s'", string(d.Bytes)) 81 | } 82 | if isForbiddenString(d.Bytes) { 83 | l.e.Err("Forbidden value in IA5String '%s'", string(d.Bytes)) 84 | } 85 | 86 | case 23: // "UTCTime" 87 | // RFC 5280 4.1.2.5: times must be in Z (GMT) 88 | if !bytes.HasSuffix(d.Bytes, []byte{90}) { 89 | l.e.Err("UTCTime not in Zulu/GMT") 90 | } 91 | if !formatUTCTime.Match(d.Bytes) { 92 | l.e.Err("Invalid UTCTime") 93 | } 94 | case 24: // "GeneralizedTime" 95 | var v time.Time 96 | _, err := asn1.Unmarshal(d.FullBytes, &v) 97 | if err != nil { 98 | l.e.Err("Failed to parse Generalized Time: %s", err.Error()) 99 | } 100 | 101 | // RFC 5280 4.1.2.5: times must be in Z (GMT) 102 | if !bytes.HasSuffix(d.Bytes, []byte{90}) { 103 | l.e.Err("Generalized Time not in Zulu/GMT") 104 | } 105 | // TODO: Can we use binary.BigEndian.Varint(d.Bytes[0:3]), for better performance? 106 | if v.Year() < 2050 { 107 | l.e.Err("Generalized Time before 2050") 108 | } 109 | 110 | if !formatGeneralizedTime.Match(d.Bytes) { 111 | l.e.Err("Invalid Generalized Time") 112 | } 113 | 114 | case 25: // "GraphicString" 115 | l.e.Warning("Using deprecated GraphicString for '%s'", string(d.Bytes)) 116 | if isForbiddenString(d.Bytes) { 117 | l.e.Err("Forbidden value in GraphicString '%s'", string(d.Bytes)) 118 | } 119 | if isControlCharacter(d.Bytes) { 120 | l.e.Err("Control character in GraphicString '%s'", string(d.Bytes)) 121 | } 122 | case 26: // "VisibleString, ISO646String" 123 | case 27: // "GeneralString" 124 | l.e.Warning("Using deprecated GeneralString for '%s'", string(d.Bytes)) 125 | if isForbiddenString(d.Bytes) { 126 | l.e.Err("Forbidden value in GeneralString '%s'", string(d.Bytes)) 127 | } 128 | if isControlCharacter(d.Bytes) { 129 | l.e.Err("Control character in GeneralString '%s'", string(d.Bytes)) 130 | } 131 | case 28: // "UniversalString" 132 | l.e.Warning("Using deprecated UniversalString for '%s'", string(d.Bytes)) 133 | if isForbiddenString(d.Bytes) { 134 | l.e.Err("Forbidden value in UniversalString '%s'", string(d.Bytes)) 135 | } 136 | if isControlCharacter(d.Bytes) { 137 | l.e.Err("Control character in UniversalString '%s'", string(d.Bytes)) 138 | } 139 | case 29: // "CHARACTER STRING" 140 | case 30: // "BMPString" 141 | l.e.Warning("Using deprecated BMPString for '%s'", string(d.Bytes)) 142 | if isForbiddenString(d.Bytes) { 143 | l.e.Err("Forbidden value in BMPString '%s'", string(d.Bytes)) 144 | } 145 | if isControlCharacter(d.Bytes) { 146 | l.e.Err("Control character in BMPString '%s'", string(d.Bytes)) 147 | } 148 | } 149 | } 150 | } 151 | 152 | // Version of isPrintable without allowing a * 153 | // Source: https://golang.org/src/encoding/asn1/asn1.go 154 | func isPrintable(b byte) bool { 155 | return 'a' <= b && b <= 'z' || 156 | 'A' <= b && b <= 'Z' || 157 | '0' <= b && b <= '9' || 158 | '\'' <= b && b <= ')' || 159 | '+' <= b && b <= '/' || 160 | b == ' ' || 161 | b == ':' || 162 | b == '=' || 163 | b == '?' 164 | } 165 | 166 | // Range from: http://www.zytrax.com/tech/ia5.html 167 | func isIA5String(b []byte) bool { 168 | for len(b) > 0 { 169 | r, size := utf8.DecodeRune(b) 170 | if r < 0 || r > 127 { 171 | return false 172 | } 173 | b = b[size:] 174 | } 175 | return true 176 | } 177 | 178 | // 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, and SPACE 179 | func isNumericString(b []byte) bool { 180 | for len(b) > 0 { 181 | r, size := utf8.DecodeRune(b) 182 | if !unicode.IsNumber(r) && !unicode.IsSpace(r) { 183 | return false 184 | } 185 | b = b[size:] 186 | } 187 | return true 188 | } 189 | 190 | // The BR state that attributes MUST NOT contain metadata such as '.', '-', ' ', 191 | // this check implements a structure wide validation for values that indication 192 | // that the field is absent, incomplete, or not applicable. 193 | // 194 | // ASCII range of forbidden metadata characters are 32 - 47, 58 -64, 91 - 96, 195 | // 123 - 126, if the value does only contain metadata this value is forbidden. 196 | // This check does also detect double characters or any combination of metadata 197 | // characters. 198 | func isForbiddenString(b []byte) bool { 199 | for len(b) == 0 { 200 | return false 201 | } 202 | 203 | switch strings.ToLower(string(b)) { 204 | case "n/a": 205 | return true 206 | } 207 | 208 | for len(b) > 0 { 209 | r, size := utf8.DecodeRune(b) 210 | if !((r >= 32 && r <= 47) || 211 | (r >= 58 && r <= 64) || 212 | (r >= 91 && r <= 96) || 213 | (r >= 123 && r <= 126)) { 214 | // non metadata character included in value 215 | return false 216 | } 217 | b = b[size:] 218 | } 219 | 220 | return true 221 | } 222 | 223 | // isControlCharacter checks if Control characters are included in the given bytes 224 | func isControlCharacter(b []byte) bool { 225 | for len(b) > 0 { 226 | r, size := utf8.DecodeRune(b) 227 | if unicode.IsControl(r) || unicode.Is(unicode.C, r) { 228 | return true 229 | } 230 | b = b[size:] 231 | } 232 | return false 233 | } 234 | -------------------------------------------------------------------------------- /certlint_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/pem" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "os/exec" 10 | "path" 11 | "path/filepath" 12 | "reflect" 13 | "testing" 14 | 15 | "github.com/globalsign/certlint/asn1" 16 | "github.com/globalsign/certlint/certdata" 17 | "github.com/globalsign/certlint/checks" 18 | "github.com/golang/groupcache/lru" 19 | ) 20 | 21 | var certBench = `-----BEGIN CERTIFICATE----- 22 | MIIFqTCCBJGgAwIBAgIMLle3b82wYk2pKYzMMA0GCSqGSIb3DQEBCwUAMGIxCzAJ 23 | BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTgwNgYDVQQDEy9H 24 | bG9iYWxTaWduIEV4dGVuZGVkIFZhbGlkYXRpb24gQ0EgLSBTSEEyNTYgLSBHMjAe 25 | Fw0xNTA3MDMwNTU2MDNaFw0xNzA3MDMwNTU2MDNaMIHSMR0wGwYDVQQPDBRQcml2 26 | YXRlIE9yZ2FuaXphdGlvbjEXMBUGA1UEBRMOMDExMC0wMS0wNDAxODExEzARBgsr 27 | BgEEAYI3PAIBAxMCSlAxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIEwVUb2t5bzEQMA4G 28 | A1UEBxMHU2hpYnV5YTEZMBcGA1UECRMQMjYtMSBTYWt1cmFnYW9rYTEcMBoGA1UE 29 | ChMTR01PIEdsb2JhbFNpZ24gSy5LLjEbMBkGA1UEAxMSd3d3Lmdsb2JhbHNpZ24u 30 | bmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnG0zOWZS5Jw4FtOB 31 | k3wsBfQP5Htu9Ki4yNDc72r6Z3F/Xbp/Gg+l62CgI00ODw+YsuXl87KdQrj6f0WO 32 | HdpHYn9GmTo9WTtwKlYGgk/l71EcbCtuQ4oPqB6QAs02Ag5cSZFjnjFkXK7fMUoq 33 | 4Ds0uoHUdb69l2LFIkK7hpXE53bIA4nnHP/z7Hu/nlUOBMs1Xs7YUbemWFgbY+vy 34 | zHuq02h4hAyWeEkRyLpgpRJ4KsDnrAsy1Me0/HpURS/HljFE1adCc0s5j167OYxt 35 | ikKARpSbs3sGHREHC3MCJBu6zm6OrDJaWR48lWFkCDzuVuLcM8UKKK/Jay9PH/kh 36 | bAgZqwIDAQABo4IB7DCCAegwDgYDVR0PAQH/BAQDAgWgMIGUBggrBgEFBQcBAQSB 37 | hzCBhDBHBggrBgEFBQcwAoY7aHR0cDovL3NlY3VyZS5nbG9iYWxzaWduLmNvbS9j 38 | YWNlcnQvZ3NleHRlbmR2YWxzaGEyZzJyMi5jcnQwOQYIKwYBBQUHMAGGLWh0dHA6 39 | Ly9vY3NwMi5nbG9iYWxzaWduLmNvbS9nc2V4dGVuZHZhbHNoYTJnMjBMBgNVHSAE 40 | RTBDMEEGCSsGAQQBoDIBATA0MDIGCCsGAQUFBwIBFiZodHRwczovL3d3dy5nbG9i 41 | YWxzaWduLmNvbS9yZXBvc2l0b3J5LzAJBgNVHRMEAjAAMEMGA1UdHwQ8MDowOKA2 42 | oDSGMmh0dHA6Ly9jcmwuZ2xvYmFsc2lnbi5jb20vZ3MvZ3NleHRlbmR2YWxzaGEy 43 | ZzIuY3JsMC0GA1UdEQQmMCSCEnd3dy5nbG9iYWxzaWduLm5ldIIOZ2xvYmFsc2ln 44 | bi5uZXQwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQWBBRU 45 | nGc1F8xta8n/GEilz7cINJcjizAfBgNVHSMEGDAWgBTaQHdDZRz4/qfj9GSCPk1D 46 | EyIxAjATBgorBgEEAdZ5AgQDAQH/BAIFADANBgkqhkiG9w0BAQsFAAOCAQEAOk2S 47 | YH/vvsrHp4FtUWXuOq+jQ8uUuUMjV9Q7xSyHkLdIba7M4Sq00jA4E8YjruwlNSxp 48 | tfy6WBz8/93RDvNodNhlokJOibmZ8X3xvXMqAPFbeX6YLjWsl4z30ZnMUi2g+SQa 49 | 4JCKbYZvTnVRY3SSIqfPYcGMNG5ErFcKfq9YdcKdmw9ZuPgJUBj2aJvnnXb91MoV 50 | 2YlSqan5qI9N6K6mkf2VuXUbw2Z6H6/A7qTLj0CGzWGscqgwUXXC4aGUPFSm78AB 51 | accZ5e1iOtNIBTVUEaQ0YxNwhZqnQO4rgBFCnEpNH6TB62hXUD6u/oP/WB48Wmek 52 | KPgaoWHMo5sOFQnw5A== 53 | -----END CERTIFICATE-----` 54 | 55 | var update = flag.Bool("update", false, "Update golden files.") 56 | 57 | func TestMain(m *testing.M) { 58 | build := exec.Command("go", "build") 59 | berr := build.Run() 60 | if berr != nil { 61 | fmt.Printf("could not build before tests: %v", berr) 62 | os.Exit(1) 63 | } 64 | 65 | retcode := m.Run() 66 | 67 | clean := exec.Command("go", "clean", "-x") 68 | cerr := clean.Run() 69 | if cerr != nil { 70 | fmt.Printf("could not cleanup after tests: %v", cerr) 71 | os.Exit(1) 72 | } 73 | 74 | os.Exit(retcode) 75 | } 76 | 77 | func TestTestData(t *testing.T) { 78 | var icaCache = lru.New(200) 79 | 80 | // TODO: Check for specific errors per certificate to be sure we don't miss one 81 | files, _ := filepath.Glob("./testdata/*.pem") 82 | for _, f := range files { 83 | fname := path.Base(f) 84 | fmt.Printf("---- %s ----\n", fname) 85 | 86 | der := getCertificate("./testdata/" + fname) 87 | if len(der) > 0 { 88 | result := do(icaCache, der, true, true) 89 | if len(result.Errors.List()) == 0 { 90 | t.Errorf("Expected some errors, got %d in %s", len(result.Errors.List()), fname) 91 | continue 92 | } 93 | for _, err := range result.Errors.List() { 94 | fmt.Printf("Priority: %s, Message: %s (%s)\n", err.Priority(), err.Error(), result.Type) 95 | } 96 | } 97 | } 98 | } 99 | 100 | func TestCLITestData(t *testing.T) { 101 | dir, err := os.Getwd() 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | testdata := path.Join(dir, "testdata") 106 | dataglob := "ev*.pem" 107 | if *update { 108 | dataglob = "*.pem" 109 | } 110 | files, _ := filepath.Glob(path.Join(testdata, dataglob)) 111 | for _, f := range files { 112 | fname := path.Base(f) 113 | t.Run(fname, func(t *testing.T) { 114 | fmt.Printf("---- CLI %s ----\n", fname) 115 | 116 | golden := path.Join(testdata, fname+".golden") 117 | crt := path.Join(testdata, fname) 118 | bin := path.Join(dir, "certlint") 119 | 120 | // raise errlevel to Alert because non-zero return causes the test to fail 121 | cli_args := []string{"-expired", "-errlevel", "alert", "-cert", crt} 122 | cmd := exec.Command(bin, cli_args...) 123 | output, err := cmd.CombinedOutput() 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | 128 | // TODO: allow for programatic refresh of golden files as a test argument 129 | if *update { 130 | ioutil.WriteFile(golden, output, 0640) 131 | t.Logf("Wrote new output to golden file: %s", golden) 132 | } 133 | 134 | actual := string(output) 135 | 136 | expected, err := ioutil.ReadFile(golden) 137 | if err != nil { 138 | t.Fatal(err) 139 | } 140 | 141 | if !reflect.DeepEqual(actual, string(expected)) { 142 | t.Fatalf("actual = %T:%s\nexpected = %T:%s\n", actual, actual, string(expected), string(expected)) 143 | } 144 | }) 145 | } 146 | } 147 | 148 | func TestCLIReturn(t *testing.T) { 149 | tests := []struct { 150 | name string 151 | args []string 152 | retv string 153 | }{ 154 | { 155 | "Error Level Info", 156 | []string{ 157 | "-expired", "-errlevel", "info", "-cert", "testdata/invalid_encoding2.pem", 158 | }, 159 | "exit status 1", 160 | }, 161 | { 162 | "Error Level Warning", 163 | []string{ 164 | "-expired", "-errlevel", "warning", "-cert", "testdata/invalid_encoding2.pem", 165 | }, 166 | "exit status 1", 167 | }, 168 | { 169 | "Error Level Critical", 170 | []string{ 171 | "-expired", "-errlevel", "critical", "-cert", "testdata/invalid_encoding2.pem", 172 | }, 173 | "exit status 0", 174 | }, 175 | { 176 | "Error Level Unspecified (default is Error)", 177 | []string{"-expired", "-cert", "testdata/invalid_encoding2.pem"}, 178 | "exit status 1", 179 | }, 180 | { 181 | "Supplied Error Level Invalid", 182 | []string{"-expired", "-errlevel", "garbage", "-cert", "testdata/invalid_encoding2.pem"}, 183 | "exit status 1", 184 | }, 185 | } 186 | 187 | dir, err := os.Getwd() 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | 192 | for _, tt := range tests { 193 | t.Run(tt.name, func(t *testing.T) { 194 | fmt.Printf("---- CLI %s ----\n", tt.name) 195 | 196 | bin := path.Join(dir, "certlint") 197 | cmd := exec.Command(bin, tt.args...) 198 | _, err = cmd.CombinedOutput() 199 | if err != nil { 200 | actual := err.Error() 201 | expected := tt.retv 202 | 203 | if !reflect.DeepEqual(actual, expected) { 204 | t.Fatalf("actual = %T:%s, expected = %T:%s", actual, actual, expected, expected) 205 | } 206 | } 207 | 208 | }) 209 | } 210 | } 211 | 212 | func BenchmarkTestData(b *testing.B) { 213 | var icaCache = lru.New(200) 214 | 215 | // TODO: Check for specific errors per certificate to be sure we don't miss one 216 | files, _ := filepath.Glob("./testdata/*.pem") 217 | for _, f := range files { 218 | fname := path.Base(f) 219 | der := getCertificate("./testdata/" + fname) 220 | if len(der) > 0 { 221 | b.Run(fname, func(b *testing.B) { 222 | for i := 0; i < b.N; i++ { 223 | do(icaCache, der, true, true) 224 | } 225 | }) 226 | } 227 | } 228 | } 229 | 230 | func BenchmarkASN1Good(b *testing.B) { 231 | block, _ := pem.Decode([]byte(certBench)) 232 | 233 | // run asn1 formatting checks b.N times 234 | for n := 0; n < b.N; n++ { 235 | al := new(asn1.Linter) 236 | al.CheckStruct(block.Bytes) 237 | } 238 | } 239 | 240 | func BenchmarkCertGood(b *testing.B) { 241 | block, _ := pem.Decode([]byte(certBench)) 242 | d, _ := certdata.Load(block.Bytes) 243 | 244 | // run certificate checks b.N times 245 | for n := 0; n < b.N; n++ { 246 | checks.Certificate.Check(d) 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /examples/specificchecks/specificchecks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/globalsign/certlint/asn1" 7 | "github.com/globalsign/certlint/certdata" 8 | "github.com/globalsign/certlint/checks" 9 | "github.com/globalsign/certlint/errors" 10 | 11 | // Check for certificate (ext) KeyUsage extension 12 | _ "github.com/globalsign/certlint/checks/extensions/extkeyusage" 13 | _ "github.com/globalsign/certlint/checks/extensions/keyusage" 14 | 15 | // Also check the parsed certificate (ext) keyusage content 16 | _ "github.com/globalsign/certlint/checks/certificate/extkeyusage" 17 | _ "github.com/globalsign/certlint/checks/certificate/keyusage" 18 | ) 19 | 20 | func main() { 21 | var e = errors.New(nil) 22 | 23 | // Certificate with no keyusage 24 | der := []byte{0x30, 0x82, 0x5, 0x9, 0x30, 0x82, 0x3, 0xf1, 0xa0, 0x3, 0x2, 25 | 0x1, 0x2, 0x2, 0xc, 0x58, 0x66, 0xb4, 0xcf, 0x2, 0x14, 0xdc, 0x22, 0x49, 0xc3, 26 | 0x8e, 0x61, 0x30, 0xd, 0x6, 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 27 | 0xb, 0x5, 0x0, 0x30, 0x66, 0x31, 0xb, 0x30, 0x9, 0x6, 0x3, 0x55, 0x4, 0x6, 28 | 0x13, 0x2, 0x42, 0x45, 0x31, 0x19, 0x30, 0x17, 0x6, 0x3, 0x55, 0x4, 0xa, 0x13, 29 | 0x10, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 0x6e, 30 | 0x76, 0x2d, 0x73, 0x61, 0x31, 0x3c, 0x30, 0x3a, 0x6, 0x3, 0x55, 0x4, 0x3, 31 | 0x13, 0x33, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x53, 0x69, 0x67, 0x6e, 0x20, 32 | 0x4f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 33 | 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x43, 0x41, 34 | 0x20, 0x2d, 0x20, 0x53, 0x48, 0x41, 0x32, 0x35, 0x36, 0x20, 0x2d, 0x20, 0x47, 35 | 0x32, 0x30, 0x1e, 0x17, 0xd, 0x31, 0x36, 0x30, 0x39, 0x31, 0x35, 0x30, 0x39, 36 | 0x32, 0x37, 0x30, 0x30, 0x5a, 0x17, 0xd, 0x31, 0x39, 0x30, 0x38, 0x32, 0x37, 37 | 0x31, 0x32, 0x31, 0x38, 0x31, 0x32, 0x5a, 0x30, 0x5d, 0x31, 0xb, 0x30, 0x9, 38 | 0x6, 0x3, 0x55, 0x4, 0x6, 0x13, 0x2, 0x4e, 0x4f, 0x31, 0xd, 0x30, 0xb, 0x6, 39 | 0x3, 0x55, 0x4, 0x8, 0x13, 0x4, 0x4f, 0x73, 0x6c, 0x6f, 0x31, 0xd, 0x30, 0xb, 40 | 0x6, 0x3, 0x55, 0x4, 0x7, 0x13, 0x4, 0x4f, 0x73, 0x6c, 0x6f, 0x31, 0x18, 0x30, 41 | 0x16, 0x6, 0x3, 0x55, 0x4, 0xa, 0x13, 0xf, 0x49, 0x6d, 0x65, 0x6e, 0x74, 0x6f, 42 | 0x20, 0x4e, 0x6f, 0x72, 0x67, 0x65, 0x20, 0x41, 0x53, 0x31, 0x16, 0x30, 0x14, 43 | 0x6, 0x3, 0x55, 0x4, 0x3, 0x13, 0xd, 0x6e, 0x65, 0x77, 0x2e, 0x69, 0x6d, 0x65, 44 | 0x6e, 0x74, 0x6f, 0x2e, 0x6e, 0x6f, 0x30, 0x82, 0x1, 0x22, 0x30, 0xd, 0x6, 45 | 0x9, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0x1, 0x5, 0x0, 0x3, 0x82, 46 | 0x1, 0xf, 0x0, 0x30, 0x82, 0x1, 0xa, 0x2, 0x82, 0x1, 0x1, 0x0, 0xca, 0xe1, 47 | 0x9, 0xdb, 0xa1, 0x83, 0xe4, 0xa2, 0x1f, 0xeb, 0xfa, 0xd2, 0xa4, 0x1d, 0x46, 48 | 0x95, 0x91, 0xc2, 0x57, 0xa8, 0xe9, 0x7e, 0xaa, 0x7f, 0xfe, 0xba, 0xa7, 0x71, 49 | 0xfe, 0xbd, 0xee, 0xdf, 0xce, 0x41, 0x50, 0xa0, 0x64, 0xab, 0xcc, 0x6c, 0x5d, 50 | 0x38, 0x79, 0xfd, 0x42, 0x88, 0x97, 0xa4, 0xf7, 0xf4, 0x33, 0x55, 0x2, 0x57, 51 | 0xee, 0x11, 0x7b, 0x5b, 0x10, 0x4a, 0x43, 0x8c, 0x21, 0xe1, 0xee, 0x44, 0xdd, 52 | 0xc5, 0x52, 0xe1, 0xae, 0x43, 0xe0, 0xea, 0xea, 0xd6, 0x29, 0xdb, 0x10, 0xb6, 53 | 0xd8, 0x95, 0x7c, 0xd, 0xbd, 0xf9, 0xc5, 0xca, 0x5e, 0xc0, 0x7f, 0x39, 0x8e, 54 | 0x5, 0x1a, 0xe9, 0xdf, 0x29, 0x58, 0xbe, 0xbe, 0xb6, 0xa6, 0x8a, 0x4, 0xfa, 55 | 0xfa, 0x8f, 0x5e, 0x13, 0x12, 0xe2, 0xb8, 0x4a, 0x1d, 0xe1, 0x9a, 0xc3, 0xe, 56 | 0xd0, 0x4c, 0x81, 0x3b, 0xfb, 0xf9, 0xeb, 0xfa, 0xff, 0xd, 0x61, 0x5f, 0x2a, 57 | 0xee, 0xc4, 0x9f, 0xce, 0x82, 0x20, 0x86, 0xa8, 0xaf, 0xe5, 0xe8, 0x84, 0xe1, 58 | 0x61, 0x3b, 0x58, 0xf6, 0xae, 0xc8, 0x8d, 0x19, 0xc3, 0x2a, 0x13, 0x5a, 0x9c, 59 | 0x5f, 0xe5, 0x1a, 0x5, 0xfa, 0x53, 0xeb, 0x4f, 0x86, 0xd9, 0x3b, 0xa0, 0x8a, 60 | 0xa, 0x48, 0x68, 0x96, 0xba, 0x95, 0x14, 0x4e, 0xbf, 0xaf, 0x5e, 0xc5, 0x42, 61 | 0x21, 0x5e, 0xc7, 0x3f, 0x4b, 0x20, 0x7d, 0x26, 0x79, 0xe, 0x41, 0xd1, 0xa8, 62 | 0xea, 0x16, 0x6e, 0x2b, 0x73, 0xc6, 0x50, 0x20, 0xf0, 0xba, 0xcc, 0x6a, 0x7c, 63 | 0x5f, 0xd7, 0x45, 0x8f, 0x25, 0x5d, 0x63, 0xfd, 0xf0, 0x2a, 0x73, 0xe6, 0x4e, 64 | 0xe2, 0xce, 0x70, 0x37, 0x3b, 0x18, 0xf3, 0x93, 0x61, 0x7b, 0x1c, 0xd7, 0x3f, 65 | 0xcc, 0x73, 0x82, 0xfe, 0xd0, 0x1b, 0xfa, 0x12, 0x49, 0xa6, 0x5b, 0xa0, 0x3b, 66 | 0xa6, 0x1, 0xa1, 0x60, 0x42, 0x6c, 0x71, 0x2, 0x3, 0x1, 0x0, 0x1, 0xa3, 0x82, 67 | 0x1, 0xbe, 0x30, 0x82, 0x1, 0xba, 0x30, 0x81, 0xa0, 0x6, 0x8, 0x2b, 0x6, 0x1, 68 | 0x5, 0x5, 0x7, 0x1, 0x1, 0x4, 0x81, 0x93, 0x30, 0x81, 0x90, 0x30, 0x4d, 0x6, 69 | 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x7, 0x30, 0x2, 0x86, 0x41, 0x68, 0x74, 0x74, 70 | 0x70, 0x3a, 0x2f, 0x2f, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x2e, 0x67, 0x6c, 71 | 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 72 | 0x63, 0x61, 0x63, 0x65, 0x72, 0x74, 0x2f, 0x67, 0x73, 0x6f, 0x72, 0x67, 0x61, 73 | 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x61, 0x6c, 0x73, 0x68, 74 | 0x61, 0x32, 0x67, 0x32, 0x72, 0x31, 0x2e, 0x63, 0x72, 0x74, 0x30, 0x3f, 0x6, 75 | 0x8, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x7, 0x30, 0x1, 0x86, 0x33, 0x68, 0x74, 0x74, 76 | 0x70, 0x3a, 0x2f, 0x2f, 0x6f, 0x63, 0x73, 0x70, 0x32, 0x2e, 0x67, 0x6c, 0x6f, 77 | 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 78 | 0x73, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 79 | 0x76, 0x61, 0x6c, 0x73, 0x68, 0x61, 0x32, 0x67, 0x32, 0x30, 0x56, 0x6, 0x3, 80 | 0x55, 0x1d, 0x20, 0x4, 0x4f, 0x30, 0x4d, 0x30, 0x41, 0x6, 0x9, 0x2b, 0x6, 0x1, 81 | 0x4, 0x1, 0xa0, 0x32, 0x1, 0x14, 0x30, 0x34, 0x30, 0x32, 0x6, 0x8, 0x2b, 0x6, 82 | 0x1, 0x5, 0x5, 0x7, 0x2, 0x1, 0x16, 0x26, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 83 | 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 84 | 0x69, 0x67, 0x6e, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x65, 0x70, 0x6f, 0x73, 85 | 0x69, 0x74, 0x6f, 0x72, 0x79, 0x2f, 0x30, 0x8, 0x6, 0x6, 0x67, 0x81, 0xc, 0x1, 86 | 0x2, 0x2, 0x30, 0x9, 0x6, 0x3, 0x55, 0x1d, 0x13, 0x4, 0x2, 0x30, 0x0, 0x30, 87 | 0x49, 0x6, 0x3, 0x55, 0x1d, 0x1f, 0x4, 0x42, 0x30, 0x40, 0x30, 0x3e, 0xa0, 88 | 0x3c, 0xa0, 0x3a, 0x86, 0x38, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, 89 | 0x72, 0x6c, 0x2e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x73, 0x69, 0x67, 0x6e, 90 | 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x73, 0x2f, 0x67, 0x73, 0x6f, 0x72, 0x67, 91 | 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x76, 0x61, 0x6c, 0x73, 92 | 0x68, 0x61, 0x32, 0x67, 0x32, 0x2e, 0x63, 0x72, 0x6c, 0x30, 0x27, 0x6, 0x3, 93 | 0x55, 0x1d, 0x11, 0x4, 0x20, 0x30, 0x1e, 0x82, 0xd, 0x6e, 0x65, 0x77, 0x2e, 94 | 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x6f, 0x2e, 0x6e, 0x6f, 0x82, 0xd, 0x77, 0x77, 95 | 0x77, 0x2e, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x6f, 0x2e, 0x6e, 0x6f, 0x30, 0x1d, 96 | 0x6, 0x3, 0x55, 0x1d, 0xe, 0x4, 0x16, 0x4, 0x14, 0x3e, 0xf4, 0xeb, 0x13, 0x6e, 97 | 0x4c, 0xfd, 0xeb, 0x55, 0x5f, 0xf7, 0x6d, 0x3e, 0x81, 0x8d, 0x43, 0x75, 0x58, 98 | 0x17, 0x4a, 0x30, 0x1f, 0x6, 0x3, 0x55, 0x1d, 0x23, 0x4, 0x18, 0x30, 0x16, 99 | 0x80, 0x14, 0x96, 0xde, 0x61, 0xf1, 0xbd, 0x1c, 0x16, 0x29, 0x53, 0x1c, 0xc0, 100 | 0xcc, 0x7d, 0x3b, 0x83, 0x0, 0x40, 0xe6, 0x1a, 0x7c, 0x30, 0xd, 0x6, 0x9, 101 | 0x2a, 0x86, 0x48, 0x86, 0xf7, 0xd, 0x1, 0x1, 0xb, 0x5, 0x0, 0x3, 0x82, 0x1, 102 | 0x1, 0x0, 0x8e, 0xe9, 0x83, 0xf7, 0x45, 0x22, 0x22, 0xd5, 0xaf, 0x7a, 0x90, 103 | 0x7, 0xb9, 0x3a, 0xff, 0xba, 0x4f, 0xe3, 0x77, 0x6d, 0x85, 0xbb, 0xb6, 0xd2, 104 | 0xdc, 0x70, 0x6a, 0x5d, 0xea, 0x64, 0x19, 0x70, 0x50, 0x75, 0xd6, 0x83, 0x9c, 105 | 0x2, 0xf5, 0x4c, 0x61, 0xd5, 0xd3, 0xd9, 0xd5, 0x4c, 0xdc, 0x8c, 0x36, 0xcc, 106 | 0x7b, 0x95, 0xe0, 0x16, 0xeb, 0x61, 0x55, 0x7, 0xaa, 0x0, 0x8b, 0xfb, 0x4d, 107 | 0xba, 0x94, 0xa3, 0x27, 0x5c, 0x1d, 0xaf, 0xad, 0xcb, 0xea, 0x29, 0xfc, 0x5c, 108 | 0x36, 0x97, 0x8b, 0xbf, 0x6e, 0x7a, 0xdf, 0x84, 0x7d, 0x87, 0xcc, 0xf8, 0x92, 109 | 0x82, 0x6c, 0xf6, 0x1c, 0x19, 0xa4, 0xcf, 0xec, 0xd, 0xa0, 0xab, 0x40, 0x98, 110 | 0x41, 0xa6, 0xf8, 0x1, 0xe5, 0x67, 0xd7, 0x95, 0x8d, 0x6e, 0x1f, 0xe9, 0xa2, 111 | 0x70, 0xd6, 0xe8, 0x9a, 0x44, 0x12, 0x43, 0xed, 0x3, 0x66, 0xa3, 0xd2, 0x11, 112 | 0xe7, 0x37, 0x67, 0x14, 0xbf, 0x86, 0x3d, 0x53, 0x62, 0x9f, 0xc1, 0xac, 0x65, 113 | 0x12, 0xc1, 0x26, 0x15, 0x3a, 0xea, 0x6c, 0xe5, 0x85, 0x63, 0x8c, 0xf1, 0xbf, 114 | 0xdb, 0x2c, 0x8, 0x1b, 0x41, 0xca, 0x3c, 0xdd, 0xab, 0x49, 0xe4, 0xc1, 0x92, 115 | 0xe7, 0x41, 0x1, 0xe4, 0x85, 0xff, 0xec, 0xde, 0x45, 0x73, 0xa, 0xfe, 0x91, 116 | 0xc7, 0x77, 0x5b, 0x6e, 0x4f, 0x36, 0x43, 0x5a, 0x80, 0x2f, 0xd9, 0xb3, 0xd0, 117 | 0x42, 0xf1, 0x7a, 0x63, 0x74, 0xe4, 0x77, 0x23, 0x37, 0x8c, 0x4d, 0x1d, 0x3d, 118 | 0x67, 0x6a, 0xb5, 0x1d, 0x1d, 0xcb, 0xb1, 0x74, 0xfb, 0x12, 0x92, 0xd5, 0x88, 119 | 0x4, 0x5c, 0xbe, 0x99, 0xd7, 0xb8, 0x53, 0xbf, 0x5c, 0x5e, 0x8e, 0xcf, 0x3, 120 | 0x78, 0x2e, 0xe2, 0x4e, 0xf0, 0xb2, 0xf, 0x11, 0x72, 0x1f, 0x16, 0x1c, 0xde, 121 | 0xa7, 0x6c, 0x27, 0xc5, 0x9, 0xe0, 0x65, 0x3, 0x5f, 0xca, 0xc1} 122 | 123 | // Check the ASN1 structure for common formatting errors 124 | al := new(asn1.Linter) 125 | e.Append(al.CheckStruct(der)) 126 | 127 | // Load and parse certificate 128 | d, err := certdata.Load(der) 129 | if err == nil { 130 | // Perform all and only the imported checks 131 | e.Append(checks.Certificate.Check(d)) 132 | } 133 | 134 | // List all errors 135 | for _, err := range e.List() { 136 | fmt.Printf("%s (%s)\n", err.Error(), d.Type) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /checks/certificate/publickey/goodkey/goodkey.go: -------------------------------------------------------------------------------- 1 | // Package goodkey copied from "github.com/letsencrypt/boulder/goodkey" 2 | // 3 | // This package is covered under the Mozilla Public License Version 2.0 4 | // 5 | // Removed depency on letsencrypt core and allow key size above 4096 6 | package goodkey 7 | 8 | import ( 9 | "crypto" 10 | "crypto/ecdsa" 11 | "crypto/elliptic" 12 | "crypto/rsa" 13 | "fmt" 14 | "math/big" 15 | "reflect" 16 | "sync" 17 | ) 18 | 19 | // To generate, run: primes 2 752 | tr '\n' , 20 | var smallPrimeInts = []int64{ 21 | 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 22 | 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 23 | 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 24 | 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 25 | 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 26 | 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 27 | 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 28 | 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 29 | 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 30 | 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 31 | 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 32 | 719, 727, 733, 739, 743, 751, 33 | } 34 | 35 | // singleton defines the object of a Singleton pattern 36 | var ( 37 | smallPrimesSingleton sync.Once 38 | smallPrimes []*big.Int 39 | ) 40 | 41 | // KeyPolicy determines which types of key may be used with various boulder 42 | // operations. 43 | type KeyPolicy struct { 44 | AllowRSA bool // Whether RSA keys should be allowed. 45 | AllowECDSANISTP256 bool // Whether ECDSA NISTP256 keys should be allowed. 46 | AllowECDSANISTP384 bool // Whether ECDSA NISTP384 keys should be allowed. 47 | } 48 | 49 | // NewKeyPolicy returns a KeyPolicy that allows RSA, ECDSA256 and ECDSA384. 50 | func NewKeyPolicy() KeyPolicy { 51 | return KeyPolicy{ 52 | AllowRSA: true, 53 | AllowECDSANISTP256: true, 54 | AllowECDSANISTP384: true, 55 | } 56 | } 57 | 58 | // GoodKey returns true if the key is acceptable for both TLS use and account 59 | // key use (our requirements are the same for either one), according to basic 60 | // strength and algorithm checking. 61 | // TODO: Support JsonWebKeys once go-jose migration is done. 62 | func (policy *KeyPolicy) GoodKey(key crypto.PublicKey) error { 63 | switch t := key.(type) { 64 | case rsa.PublicKey: 65 | return policy.goodKeyRSA(t) 66 | case *rsa.PublicKey: 67 | return policy.goodKeyRSA(*t) 68 | case ecdsa.PublicKey: 69 | return policy.goodKeyECDSA(t) 70 | case *ecdsa.PublicKey: 71 | return policy.goodKeyECDSA(*t) 72 | default: 73 | return fmt.Errorf("Unknown key type %s", reflect.TypeOf(key)) 74 | } 75 | } 76 | 77 | // GoodKeyECDSA determines if an ECDSA pubkey meets our requirements 78 | func (policy *KeyPolicy) goodKeyECDSA(key ecdsa.PublicKey) (err error) { 79 | // Check the curve. 80 | // 81 | // The validity of the curve is an assumption for all following tests. 82 | err = policy.goodCurve(key.Curve) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | // Key validation routine adapted from NIST SP800-56A § 5.6.2.3.2. 88 | // 89 | // 90 | // Assuming a prime field since a) we are only allowing such curves and b) 91 | // crypto/elliptic only supports prime curves. Where this assumption 92 | // simplifies the code below, it is explicitly stated and explained. If ever 93 | // adapting this code to support non-prime curves, refer to NIST SP800-56A § 94 | // 5.6.2.3.2 and adapt this code appropriately. 95 | params := key.Params() 96 | 97 | // SP800-56A § 5.6.2.3.2 Step 1. 98 | // Partial check of the public key for an invalid range in the EC group: 99 | // Verify that key is not the point at infinity O. 100 | // This code assumes that the point at infinity is (0,0), which is the 101 | // case for all supported curves. 102 | if isPointAtInfinityNISTP(key.X, key.Y) { 103 | return fmt.Errorf("Key x, y must not be the point at infinity") 104 | } 105 | 106 | // SP800-56A § 5.6.2.3.2 Step 2. 107 | // "Verify that x_Q and y_Q are integers in the interval [0,p-1] in the 108 | // case that q is an odd prime p, or that x_Q and y_Q are bit strings 109 | // of length m bits in the case that q = 2**m." 110 | // 111 | // Prove prime field: ASSUMED. 112 | // Prove q != 2: ASSUMED. (Curve parameter. No supported curve has q == 2.) 113 | // Prime field && q != 2 => q is an odd prime p 114 | // Therefore "verify that x, y are in [0, p-1]" satisfies step 2. 115 | // 116 | // Therefore verify that both x and y of the public key point have the unique 117 | // correct representation of an element in the underlying field by verifying 118 | // that x and y are integers in [0, p-1]. 119 | if key.X.Sign() < 0 || key.Y.Sign() < 0 { 120 | return fmt.Errorf("Key x, y must not be negative") 121 | } 122 | 123 | if key.X.Cmp(params.P) >= 0 || key.Y.Cmp(params.P) >= 0 { 124 | return fmt.Errorf("Key x, y must not exceed P-1") 125 | } 126 | 127 | // SP800-56A § 5.6.2.3.2 Step 3. 128 | // "If q is an odd prime p, verify that (y_Q)**2 === (x_Q)***3 + a*x_Q + b (mod p). 129 | // If q = 2**m, verify that (y_Q)**2 + (x_Q)*(y_Q) == (x_Q)**3 + a*(x_Q)*2 + b in 130 | // the finite field of size 2**m. 131 | // (Ensures that the public key is on the correct elliptic curve.)" 132 | // 133 | // q is an odd prime p: proven/assumed above. 134 | // a = -3 for all supported curves. 135 | // 136 | // Therefore step 3 is satisfied simply by showing that 137 | // y**2 === x**3 - 3*x + B (mod P). 138 | // 139 | // This proves that the public key is on the correct elliptic curve. 140 | // But in practice, this test is provided by crypto/elliptic, so use that. 141 | if !key.Curve.IsOnCurve(key.X, key.Y) { 142 | return fmt.Errorf("Key point is not on the curve") 143 | } 144 | 145 | // SP800-56A § 5.6.2.3.2 Step 4. 146 | // "Verify that n*Q == O. 147 | // (Ensures that the public key has the correct order. Along with check 1, 148 | // ensures that the public key is in the correct range in the correct EC 149 | // subgroup, that is, it is in the correct EC subgroup and is not the 150 | // identity element.)" 151 | // 152 | // Ensure that public key has the correct order: 153 | // verify that n*Q = O. 154 | // 155 | // n*Q = O iff n*Q is the point at infinity (see step 1). 156 | ox, oy := key.Curve.ScalarMult(key.X, key.Y, params.N.Bytes()) 157 | if !isPointAtInfinityNISTP(ox, oy) { 158 | return fmt.Errorf("Public key does not have correct order") 159 | } 160 | 161 | // End of SP800-56A § 5.6.2.3.2 Public Key Validation Routine. 162 | // Key is valid. 163 | return nil 164 | } 165 | 166 | // Returns true iff the point (x,y) on NIST P-256, NIST P-384 or NIST P-521 is 167 | // the point at infinity. These curves all have the same point at infinity 168 | // (0,0). This function must ONLY be used on points on curves verified to have 169 | // (0,0) as their point at infinity. 170 | func isPointAtInfinityNISTP(x, y *big.Int) bool { 171 | return x.Sign() == 0 && y.Sign() == 0 172 | } 173 | 174 | // GoodCurve determines if an elliptic curve meets our requirements. 175 | func (policy *KeyPolicy) goodCurve(c elliptic.Curve) (err error) { 176 | // Simply use a whitelist for now. 177 | params := c.Params() 178 | switch { 179 | case policy.AllowECDSANISTP256 && params == elliptic.P256().Params(): 180 | return nil 181 | case policy.AllowECDSANISTP384 && params == elliptic.P384().Params(): 182 | return nil 183 | default: 184 | return fmt.Errorf(fmt.Sprintf("ECDSA curve %v not allowed", params.Name)) 185 | } 186 | } 187 | 188 | // GoodKeyRSA determines if a RSA pubkey meets our requirements 189 | func (policy *KeyPolicy) goodKeyRSA(key rsa.PublicKey) (err error) { 190 | if !policy.AllowRSA { 191 | return fmt.Errorf("RSA keys are not allowed") 192 | } 193 | 194 | // Baseline Requirements Appendix A 195 | // Modulus must be >= 2048 bits 196 | // Removed max keySize from original package version 197 | modulus := key.N 198 | modulusBitLen := modulus.BitLen() 199 | if modulusBitLen < 2048 { 200 | return fmt.Errorf(fmt.Sprintf("Key too small: %d", modulusBitLen)) 201 | } 202 | // Bit lengths that are not a multiple of 8 may cause problems on some 203 | // client implementations. 204 | if modulusBitLen%8 != 0 { 205 | return fmt.Errorf(fmt.Sprintf("Key length wasn't a multiple of 8: %d", modulusBitLen)) 206 | } 207 | // The CA SHALL confirm that the value of the public exponent is an 208 | // odd number equal to 3 or more. Additionally, the public exponent 209 | // SHOULD be in the range between 2^16 + 1 and 2^256-1. 210 | // NOTE: rsa.PublicKey cannot represent an exponent part greater than 211 | // 2^32 - 1 or 2^64 - 1, because it stores E as an integer. So we 212 | // don't need to check the upper bound. 213 | if (key.E%2) == 0 || key.E < ((1<<16)+1) { 214 | return fmt.Errorf(fmt.Sprintf("Key exponent should be odd and >2^16: %d", key.E)) 215 | } 216 | // The modulus SHOULD also have the following characteristics: an odd 217 | // number, not the power of a prime, and have no factors smaller than 752. 218 | // TODO: We don't yet check for "power of a prime." 219 | if checkSmallPrimes(modulus) { 220 | return fmt.Errorf("Key divisible by small prime") 221 | } 222 | 223 | return nil 224 | } 225 | 226 | // Returns true iff integer i is divisible by any of the primes in smallPrimes. 227 | // 228 | // Short circuits; execution time is dependent on i. Do not use this on secret 229 | // values. 230 | func checkSmallPrimes(i *big.Int) bool { 231 | smallPrimesSingleton.Do(func() { 232 | for _, prime := range smallPrimeInts { 233 | smallPrimes = append(smallPrimes, big.NewInt(prime)) 234 | } 235 | }) 236 | 237 | for _, prime := range smallPrimes { 238 | var result big.Int 239 | result.Mod(i, prime) 240 | if result.Sign() == 0 { 241 | return true 242 | } 243 | } 244 | 245 | return false 246 | } 247 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 GMO GlobalSign Ltd. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /certlint.go: -------------------------------------------------------------------------------- 1 | // A command line utility that uses the certlint library to validate one or more 2 | // certificates. 3 | // 4 | // See the examples directory for other use cases. 5 | 6 | package main 7 | 8 | import ( 9 | "bufio" 10 | "bytes" 11 | "crypto/sha1" 12 | "crypto/sha256" 13 | "crypto/x509" 14 | "encoding/csv" 15 | "encoding/hex" 16 | "encoding/pem" 17 | "flag" 18 | "fmt" 19 | "io/ioutil" 20 | "net/http" 21 | "os" 22 | "runtime" 23 | "strings" 24 | "sync" 25 | "time" 26 | 27 | "github.com/globalsign/certlint/asn1" 28 | "github.com/globalsign/certlint/certdata" 29 | "github.com/globalsign/certlint/checks" 30 | "github.com/globalsign/certlint/errors" 31 | 32 | // Import all available checks 33 | _ "github.com/globalsign/certlint/checks/certificate/all" 34 | _ "github.com/globalsign/certlint/checks/extensions/all" 35 | 36 | "github.com/cloudflare/cfssl/log" 37 | "github.com/cloudflare/cfssl/revoke" 38 | "github.com/golang/groupcache/lru" 39 | 40 | "github.com/pkg/profile" 41 | ) 42 | 43 | type testResult struct { 44 | Type string 45 | Trusted bool 46 | Cert *x509.Certificate 47 | Pem string 48 | Der []byte 49 | Errors *errors.Errors 50 | } 51 | 52 | // if errors package changes, this must change 53 | var priorityMap = map[string]errors.Priority{ 54 | "debug": errors.Debug, 55 | "info": errors.Info, 56 | "notice": errors.Notice, 57 | "warning": errors.Warning, 58 | "error": errors.Error, 59 | "critical": errors.Critical, 60 | "alert": errors.Alert, 61 | "emergency": errors.Emergency, 62 | } 63 | 64 | var jobs = make(chan []byte, 100) 65 | var results = make(chan testResult, 100) 66 | var count int64 67 | var saved int64 68 | var wgSave sync.WaitGroup 69 | var wgBulk sync.WaitGroup 70 | var intPool *x509.CertPool 71 | var trusted bool 72 | 73 | func main() { 74 | var cert = flag.String("cert", "", "Certificate file") 75 | var bulk = flag.String("bulk", "", "Bulk certificates file") 76 | var issuer = flag.String("issuer", "", "Pem file with one or more issuers") 77 | var expired = flag.Bool("expired", false, "Test expired certificates") 78 | var report = flag.String("report", "report.csv", "Report filename") 79 | var include = flag.Bool("include", false, "Include certificates in report") 80 | var revoked = flag.Bool("revoked", false, "Check if certificates are revoked") 81 | trusted = *flag.Bool("trusted", false, "Only check trusted certificates") 82 | var flagErr = flag.String("errlevel", "error", "Exit non-zero for Errors at this level") 83 | var pprof = flag.String("pprof", "", "Generate pprof profile (cpu,mem,trace)") 84 | var help = flag.Bool("help", false, "Show this help") 85 | 86 | flag.Parse() 87 | 88 | if *help || (len(*cert) < 1 && len(*bulk) < 1) { 89 | flag.PrintDefaults() 90 | return 91 | } 92 | 93 | errlevel := strings.ToLower(*flagErr) 94 | // Sanity-check for flagErr 95 | if _, included := priorityMap[errlevel]; !included { 96 | fmt.Println("Supplied -errlevel is invalid") 97 | os.Exit(1) 98 | } 99 | 100 | // Is any profiling requested? 101 | switch *pprof { 102 | case "cpu": 103 | defer profile.Start(profile.CPUProfile).Stop() 104 | case "mem": 105 | defer profile.Start(profile.MemProfile).Stop() 106 | case "trace": 107 | defer profile.Start(profile.TraceProfile).Stop() 108 | default: 109 | // pprof disabled 110 | } 111 | 112 | // Prevent CloudFlare informational log messages 113 | log.Level = log.LevelError 114 | 115 | // Load intermediates 116 | if len(*issuer) > 0 { 117 | data, err := ioutil.ReadFile(*issuer) 118 | if err != nil { 119 | log.Fatal("Failed to load intermediates:", err) 120 | } 121 | intPool = x509.NewCertPool() 122 | intPool.AppendCertsFromPEM(data) 123 | } 124 | 125 | // Start the bulk checking logic to parse a pem file with more certificates and 126 | // save the results to a csv file. 127 | if len(*bulk) > 0 { 128 | wgSave.Add(1) 129 | go saveResults(*report, *include, *revoked) 130 | 131 | for i := 1; i <= runtime.NumCPU(); i++ { 132 | wgBulk.Add(1) 133 | go runBulk(*expired) 134 | } 135 | 136 | doBulk(*bulk) 137 | 138 | fmt.Println("Finshed reading bulk file, waiting for processing to finish") 139 | wgBulk.Wait() 140 | 141 | close(results) 142 | 143 | fmt.Println("Processing finished, waiting till all results are saved") 144 | wgSave.Wait() 145 | 146 | return 147 | } 148 | 149 | // Check one certificate and print results on screen 150 | der := getCertificate(*cert) 151 | result := do(nil, der, *expired, true) 152 | 153 | fmt.Println("Processed Certificate Type:", result.Type) 154 | if result.Errors != nil { 155 | fmt.Printf("Certificate Errors: %d\n", len(result.Errors.List())) 156 | for _, err := range result.Errors.List() { 157 | fmt.Printf(" Priority: %s, Message: %s\n", err.Priority(), err.Error()) 158 | } 159 | if result.Errors.Priority() >= priorityMap[errlevel] { 160 | os.Exit(1) 161 | } 162 | } 163 | } 164 | 165 | // do performs the checks on the der encoding and the actual certificate, if exp 166 | // is set true it will also check expired certificates. 167 | func do(icaCache *lru.Cache, der []byte, exp, rtrn bool) testResult { 168 | // use a local cache to prevent that we need to wait on a local 169 | var result testResult 170 | result.Errors = errors.New(nil) 171 | 172 | // Include der in results for debugging 173 | result.Der = der 174 | 175 | // This causes that we check every certificate, even expired certificates 176 | al := new(asn1.Linter) 177 | result.Errors.Append(al.CheckStruct(der)) 178 | 179 | // Load certificate 180 | d, err := certdata.Load(der) 181 | if err != nil { 182 | result.Errors.Err(err.Error()) 183 | } else { 184 | result.Trusted = true 185 | result.Cert = d.Cert 186 | result.Type = d.Type 187 | 188 | // Indication to not check this type of certificate 189 | if d.Type == "-" { 190 | return result 191 | } 192 | 193 | // Check if we need to skip expired certificates 194 | if !exp && d.Cert.NotAfter.Before(time.Now()) { 195 | return result 196 | } 197 | 198 | // Check if this is a publicly trusted certificate 199 | opts := x509.VerifyOptions{ 200 | CurrentTime: d.Cert.NotBefore, 201 | Intermediates: intPool, 202 | KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, 203 | } 204 | 205 | chain, err := d.Cert.Verify(opts) 206 | if err == nil && len(chain) > 0 && len(chain[0]) > 1 { 207 | d.Issuer = chain[0][1] 208 | 209 | } else { 210 | // Issuer not in default pool, use issuer from AIA cache, download if 211 | // not in cache and when certificate has not expired. 212 | pool := intPool 213 | type issuerCache struct { 214 | Trusted bool 215 | Issuer *x509.Certificate 216 | Pool *x509.CertPool 217 | } 218 | 219 | var key string 220 | 221 | // Create a unique ID to cache the chain of this issuer 222 | if len(d.Cert.IssuingCertificateURL) > 0 { 223 | // Same issuer can have multiple issuing URL's (cross certificates), we 224 | // want to test with the provided information 225 | key = fmt.Sprintf("%x", sha1.Sum([]byte(fmt.Sprint(d.Cert.IssuingCertificateURL)))) 226 | 227 | } else if len(d.Cert.AuthorityKeyId) > 0 { 228 | // If no issuer is given we use the AuthorityKeyId to identify the chain 229 | key = fmt.Sprintf("%x", d.Cert.AuthorityKeyId) 230 | 231 | } else { 232 | // If we also have no AKI the only thing left is the raw DN of the issuer 233 | key = fmt.Sprintf("%x", sha1.Sum(d.Cert.RawIssuer)) 234 | } 235 | 236 | // try to get from lru cache 237 | var cache interface{} 238 | var ok bool 239 | 240 | if icaCache != nil { 241 | cache, ok = icaCache.Get(key) 242 | } 243 | if ok { 244 | ic := cache.(issuerCache) 245 | result.Trusted = ic.Trusted 246 | d.Issuer = ic.Issuer 247 | pool = ic.Pool 248 | 249 | } else { 250 | var e = errors.New(nil) 251 | d.Issuer, pool, e = getIssuerPool(d.Cert) 252 | result.Errors.Append(e) 253 | 254 | // Check if this is a publicly trusted certificate 255 | opts := x509.VerifyOptions{ 256 | CurrentTime: d.Cert.NotBefore, 257 | Intermediates: pool, 258 | KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, 259 | } 260 | if _, err = d.Cert.Verify(opts); err != nil { 261 | result.Trusted = false 262 | } 263 | 264 | // Save pool in cache 265 | if pool != nil && icaCache != nil { 266 | icaCache.Add(key, issuerCache{result.Trusted, d.Issuer, pool}) 267 | } 268 | } 269 | } 270 | 271 | if trusted && !result.Trusted { 272 | fmt.Printf("Failed to verify chain for %s\n", d.Cert.Issuer.CommonName) 273 | result.Errors.Err("Failed to verify chain for %s\n", d.Cert.Issuer.CommonName) 274 | return result 275 | } 276 | 277 | if d.Issuer == nil { 278 | fmt.Printf("Incomplete chain for %s %s %x %v\n", d.Cert.Issuer.CommonName, d.Cert.Subject.CommonName, d.Cert.SerialNumber, result.Errors) 279 | } 280 | 281 | // Check against errors 282 | result.Errors.Append(checks.Certificate.Check(d)) 283 | } 284 | 285 | // In batch mode we want to queue results 286 | if !rtrn && result.Errors.IsError() { 287 | results <- result 288 | } 289 | 290 | return result 291 | } 292 | 293 | func doBulk(bulk string) { 294 | var pemCert []byte 295 | 296 | f, err := os.Open(bulk) 297 | if err != nil { 298 | fmt.Println(err) 299 | return 300 | } 301 | 302 | // Unfortunately pem.Decode can't use a io.Reader but exspects a byte array 303 | // the files we want to support are to big to load in memory. 304 | scanner := bufio.NewScanner(f) 305 | for scanner.Scan() { 306 | line := scanner.Bytes() 307 | 308 | // "-BEGIN CERTIFICATE-" 309 | if bytes.Contains(line, []byte{0x2d, 0x42, 0x45, 0x47, 0x49, 0x4e, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d}) { 310 | pemCert = []byte{} 311 | } 312 | 313 | pemCert = append(pemCert, []byte{0xa}...) 314 | pemCert = append(pemCert, line...) 315 | 316 | // Check last line for "-END CERTIFICATE-" 317 | if bytes.Contains(line, []byte{0x2d, 0x45, 0x4e, 0x44, 0x20, 0x43, 0x45, 0x52, 0x54, 0x49, 0x46, 0x49, 0x43, 0x41, 0x54, 0x45, 0x2d}) { 318 | block, _ := pem.Decode(pemCert) 319 | if block != nil { 320 | count++ 321 | jobs <- block.Bytes 322 | } else { 323 | fmt.Println(string(pemCert)) 324 | var e = errors.New(nil) 325 | if err != nil { 326 | e.Err(err.Error()) 327 | } 328 | 329 | results <- testResult{ 330 | Cert: nil, 331 | Pem: string(pemCert), 332 | Errors: e, 333 | } 334 | } 335 | } 336 | } 337 | 338 | fmt.Printf("Checked %d certificates\n", count) 339 | close(jobs) 340 | } 341 | 342 | func runBulk(exp bool) { 343 | defer wgBulk.Done() 344 | var icaCache = lru.New(200) 345 | 346 | for { 347 | der, more := <-jobs 348 | if more { 349 | do(icaCache, der, exp, false) 350 | } else { 351 | break 352 | } 353 | } 354 | } 355 | 356 | func saveResults(filename string, include, revoked bool) error { 357 | defer wgSave.Done() 358 | 359 | file, err := os.Create(filename) 360 | if err != nil { 361 | fmt.Println(err) 362 | return err 363 | } 364 | defer file.Close() 365 | 366 | writer := csv.NewWriter(file) 367 | writer.UseCRLF = true 368 | writer.Write([]string{"Issuer", "CN", "O", "Serial", "NotBefore", "NotAfter", "Type", "Priority", "Error", "Revoked", "Cert", "Fingerprint"}) 369 | writer.Flush() 370 | 371 | for { 372 | r, more := <-results 373 | if !more { 374 | break 375 | } 376 | 377 | // Don't report anything less than warning (info, debug, notice) 378 | if r.Errors.Priority() < errors.Warning { 379 | continue 380 | } 381 | 382 | // Add all errors to file 383 | for _, e := range r.Errors.List() { 384 | var columns []string 385 | if r.Cert != nil { 386 | columns = []string{ 387 | fmt.Sprintf("%s, %s", r.Cert.Issuer.CommonName, r.Cert.Issuer.Organization), 388 | r.Cert.Subject.CommonName, 389 | strings.Join(r.Cert.Subject.Organization, ", "), 390 | hex.EncodeToString(r.Cert.SerialNumber.Bytes()), 391 | r.Cert.NotBefore.Format("2006-01-02"), 392 | r.Cert.NotAfter.Format("2006-01-02"), 393 | r.Type, 394 | e.Priority().String(), 395 | e.Error(), 396 | } 397 | 398 | // Check if certificate is revoked when idicated and not expired 399 | if revoked { 400 | if r.Cert.NotAfter.Before(time.Now()) { 401 | // Expired certs are often purged of the revocation list/status 402 | columns = append(columns, "expired") 403 | } else if isRevoked, ok := revoke.VerifyCertificate(r.Cert); ok { 404 | columns = append(columns, fmt.Sprintf("%t", isRevoked)) 405 | } else { 406 | columns = append(columns, "failed") 407 | } 408 | } else { 409 | columns = append(columns, "") 410 | } 411 | 412 | // Do we need to include the certificate 413 | if include { 414 | columns = append(columns, string(pem.EncodeToMemory(&pem.Block{ 415 | Type: "CERTIFICATE", 416 | Bytes: r.Der, 417 | }))) 418 | } else { 419 | columns = append(columns, "") 420 | } 421 | 422 | // Certificate Fingerprint 423 | fingerprint := sha256.Sum256(r.Der) 424 | columns = append(columns, hex.EncodeToString(fingerprint[:])) 425 | 426 | } else { 427 | columns = []string{"", "", "", "", "", "", "", e.Priority().String(), e.Error(), "", r.Pem} 428 | } 429 | 430 | err := writer.Write(columns) 431 | if err != nil { 432 | fmt.Println(err) 433 | continue 434 | } 435 | 436 | saved++ 437 | } 438 | } 439 | 440 | writer.Flush() 441 | 442 | fmt.Printf("Saved %d findings\n", saved) 443 | return nil 444 | } 445 | 446 | // getCertificate reads a single certificate from disk 447 | func getCertificate(file string) []byte { 448 | derBytes, err := ioutil.ReadFile(file) 449 | if err != nil { 450 | fmt.Println(err) 451 | return nil 452 | } 453 | // decode pem 454 | block, _ := pem.Decode(derBytes) 455 | if block != nil { 456 | derBytes = block.Bytes 457 | } 458 | return derBytes 459 | } 460 | 461 | func getIssuerPool(cert *x509.Certificate) (*x509.Certificate, *x509.CertPool, *errors.Errors) { 462 | var e = errors.New(nil) 463 | var issuer *x509.Certificate 464 | 465 | pool := x509.NewCertPool() 466 | var i int 467 | for len(cert.IssuingCertificateURL) > 0 { 468 | ic, err := getIssuer(cert) 469 | e.Append(err) 470 | if ic == nil { 471 | break 472 | } 473 | 474 | // add certificate to pool 475 | pool.AddCert(ic) 476 | 477 | // issuer of end-entity certificate 478 | if i == 0 { 479 | issuer = ic 480 | } 481 | 482 | // download the issuer of the issuer certificate 483 | cert = ic 484 | i++ 485 | } 486 | 487 | return issuer, pool, e 488 | } 489 | 490 | func getIssuer(cert *x509.Certificate) (*x509.Certificate, *errors.Errors) { 491 | var e = errors.New(nil) 492 | var issuer *x509.Certificate 493 | for _, url := range cert.IssuingCertificateURL { 494 | // download if not in cache 495 | var err error 496 | issuer, err = downloadCert(url) 497 | if err != nil { 498 | e.Err("Failed to download issuer certificate from '%s': %s", url, err.Error()) 499 | } 500 | if issuer != nil { 501 | break 502 | } 503 | } 504 | 505 | // check if the signature on this certificate can be verified with the downloaded issuer certificate 506 | if issuer != nil { 507 | err := cert.CheckSignatureFrom(issuer) 508 | if err != nil { 509 | e.Err("Signature not from downloaded issuer: %s", err.Error()) 510 | } 511 | } 512 | 513 | return issuer, e 514 | } 515 | 516 | func downloadCert(url string) (*x509.Certificate, error) { 517 | // download file 518 | resp, err := http.Get(url) 519 | if err != nil { 520 | return nil, err 521 | } 522 | 523 | if resp.StatusCode > 399 { 524 | return nil, fmt.Errorf("Unexpected response '%s'", resp.Status) 525 | } 526 | 527 | // read response body 528 | derBytes, err := ioutil.ReadAll(resp.Body) 529 | if err != nil { 530 | return nil, err 531 | } 532 | resp.Body.Close() 533 | 534 | // decode pem, if pem 535 | block, _ := pem.Decode(derBytes) 536 | if block != nil { 537 | derBytes = block.Bytes 538 | } 539 | 540 | issuer, err := x509.ParseCertificate(derBytes) 541 | if err != nil { 542 | return nil, err 543 | } 544 | 545 | return issuer, nil 546 | } 547 | -------------------------------------------------------------------------------- /checks/certificate/publickey/goodkey/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016 ISRG. All rights reserved. 2 | 3 | Mozilla Public License Version 2.0 4 | ================================== 5 | 6 | 1. Definitions 7 | -------------- 8 | 9 | 1.1. "Contributor" 10 | means each individual or legal entity that creates, contributes to 11 | the creation of, or owns Covered Software. 12 | 13 | 1.2. "Contributor Version" 14 | means the combination of the Contributions of others (if any) used 15 | by a Contributor and that particular Contributor's Contribution. 16 | 17 | 1.3. "Contribution" 18 | means Covered Software of a particular Contributor. 19 | 20 | 1.4. "Covered Software" 21 | means Source Code Form to which the initial Contributor has attached 22 | the notice in Exhibit A, the Executable Form of such Source Code 23 | Form, and Modifications of such Source Code Form, in each case 24 | including portions thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | (a) that the initial Contributor has attached the notice described 30 | in Exhibit B to the Covered Software; or 31 | 32 | (b) that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the 34 | terms of a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | means any form of the work other than Source Code Form. 38 | 39 | 1.7. "Larger Work" 40 | means a work that combines Covered Software with other material, in 41 | a separate file or files, that is not Covered Software. 42 | 43 | 1.8. "License" 44 | means this document. 45 | 46 | 1.9. "Licensable" 47 | means having the right to grant, to the maximum extent possible, 48 | whether at the time of the initial grant or subsequently, any and 49 | all of the rights conveyed by this License. 50 | 51 | 1.10. "Modifications" 52 | means any of the following: 53 | 54 | (a) any file in Source Code Form that results from an addition to, 55 | deletion from, or modification of the contents of Covered 56 | Software; or 57 | 58 | (b) any new file in Source Code Form that contains any Covered 59 | Software. 60 | 61 | 1.11. "Patent Claims" of a Contributor 62 | means any patent claim(s), including without limitation, method, 63 | process, and apparatus claims, in any patent Licensable by such 64 | Contributor that would be infringed, but for the grant of the 65 | License, by the making, using, selling, offering for sale, having 66 | made, import, or transfer of either its Contributions or its 67 | Contributor Version. 68 | 69 | 1.12. "Secondary License" 70 | means either the GNU General Public License, Version 2.0, the GNU 71 | Lesser General Public License, Version 2.1, the GNU Affero General 72 | Public License, Version 3.0, or any later versions of those 73 | licenses. 74 | 75 | 1.13. "Source Code Form" 76 | means the form of the work preferred for making modifications. 77 | 78 | 1.14. "You" (or "Your") 79 | means an individual or a legal entity exercising rights under this 80 | License. For legal entities, "You" includes any entity that 81 | controls, is controlled by, or is under common control with You. For 82 | purposes of this definition, "control" means (a) the power, direct 83 | or indirect, to cause the direction or management of such entity, 84 | whether by contract or otherwise, or (b) ownership of more than 85 | fifty percent (50%) of the outstanding shares or beneficial 86 | ownership of such entity. 87 | 88 | 2. License Grants and Conditions 89 | -------------------------------- 90 | 91 | 2.1. Grants 92 | 93 | Each Contributor hereby grants You a world-wide, royalty-free, 94 | non-exclusive license: 95 | 96 | (a) under intellectual property rights (other than patent or trademark) 97 | Licensable by such Contributor to use, reproduce, make available, 98 | modify, display, perform, distribute, and otherwise exploit its 99 | Contributions, either on an unmodified basis, with Modifications, or 100 | as part of a Larger Work; and 101 | 102 | (b) under Patent Claims of such Contributor to make, use, sell, offer 103 | for sale, have made, import, and otherwise transfer either its 104 | Contributions or its Contributor Version. 105 | 106 | 2.2. Effective Date 107 | 108 | The licenses granted in Section 2.1 with respect to any Contribution 109 | become effective for each Contribution on the date the Contributor first 110 | distributes such Contribution. 111 | 112 | 2.3. Limitations on Grant Scope 113 | 114 | The licenses granted in this Section 2 are the only rights granted under 115 | this License. No additional rights or licenses will be implied from the 116 | distribution or licensing of Covered Software under this License. 117 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 118 | Contributor: 119 | 120 | (a) for any code that a Contributor has removed from Covered Software; 121 | or 122 | 123 | (b) for infringements caused by: (i) Your and any other third party's 124 | modifications of Covered Software, or (ii) the combination of its 125 | Contributions with other software (except as part of its Contributor 126 | Version); or 127 | 128 | (c) under Patent Claims infringed by Covered Software in the absence of 129 | its Contributions. 130 | 131 | This License does not grant any rights in the trademarks, service marks, 132 | or logos of any Contributor (except as may be necessary to comply with 133 | the notice requirements in Section 3.4). 134 | 135 | 2.4. Subsequent Licenses 136 | 137 | No Contributor makes additional grants as a result of Your choice to 138 | distribute the Covered Software under a subsequent version of this 139 | License (see Section 10.2) or under the terms of a Secondary License (if 140 | permitted under the terms of Section 3.3). 141 | 142 | 2.5. Representation 143 | 144 | Each Contributor represents that the Contributor believes its 145 | Contributions are its original creation(s) or it has sufficient rights 146 | to grant the rights to its Contributions conveyed by this License. 147 | 148 | 2.6. Fair Use 149 | 150 | This License is not intended to limit any rights You have under 151 | applicable copyright doctrines of fair use, fair dealing, or other 152 | equivalents. 153 | 154 | 2.7. Conditions 155 | 156 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 157 | in Section 2.1. 158 | 159 | 3. Responsibilities 160 | ------------------- 161 | 162 | 3.1. Distribution of Source Form 163 | 164 | All distribution of Covered Software in Source Code Form, including any 165 | Modifications that You create or to which You contribute, must be under 166 | the terms of this License. You must inform recipients that the Source 167 | Code Form of the Covered Software is governed by the terms of this 168 | License, and how they can obtain a copy of this License. You may not 169 | attempt to alter or restrict the recipients' rights in the Source Code 170 | Form. 171 | 172 | 3.2. Distribution of Executable Form 173 | 174 | If You distribute Covered Software in Executable Form then: 175 | 176 | (a) such Covered Software must also be made available in Source Code 177 | Form, as described in Section 3.1, and You must inform recipients of 178 | the Executable Form how they can obtain a copy of such Source Code 179 | Form by reasonable means in a timely manner, at a charge no more 180 | than the cost of distribution to the recipient; and 181 | 182 | (b) You may distribute such Executable Form under the terms of this 183 | License, or sublicense it under different terms, provided that the 184 | license for the Executable Form does not attempt to limit or alter 185 | the recipients' rights in the Source Code Form under this License. 186 | 187 | 3.3. Distribution of a Larger Work 188 | 189 | You may create and distribute a Larger Work under terms of Your choice, 190 | provided that You also comply with the requirements of this License for 191 | the Covered Software. If the Larger Work is a combination of Covered 192 | Software with a work governed by one or more Secondary Licenses, and the 193 | Covered Software is not Incompatible With Secondary Licenses, this 194 | License permits You to additionally distribute such Covered Software 195 | under the terms of such Secondary License(s), so that the recipient of 196 | the Larger Work may, at their option, further distribute the Covered 197 | Software under the terms of either this License or such Secondary 198 | License(s). 199 | 200 | 3.4. Notices 201 | 202 | You may not remove or alter the substance of any license notices 203 | (including copyright notices, patent notices, disclaimers of warranty, 204 | or limitations of liability) contained within the Source Code Form of 205 | the Covered Software, except that You may alter any license notices to 206 | the extent required to remedy known factual inaccuracies. 207 | 208 | 3.5. Application of Additional Terms 209 | 210 | You may choose to offer, and to charge a fee for, warranty, support, 211 | indemnity or liability obligations to one or more recipients of Covered 212 | Software. However, You may do so only on Your own behalf, and not on 213 | behalf of any Contributor. You must make it absolutely clear that any 214 | such warranty, support, indemnity, or liability obligation is offered by 215 | You alone, and You hereby agree to indemnify every Contributor for any 216 | liability incurred by such Contributor as a result of warranty, support, 217 | indemnity or liability terms You offer. You may include additional 218 | disclaimers of warranty and limitations of liability specific to any 219 | jurisdiction. 220 | 221 | 4. Inability to Comply Due to Statute or Regulation 222 | --------------------------------------------------- 223 | 224 | If it is impossible for You to comply with any of the terms of this 225 | License with respect to some or all of the Covered Software due to 226 | statute, judicial order, or regulation then You must: (a) comply with 227 | the terms of this License to the maximum extent possible; and (b) 228 | describe the limitations and the code they affect. Such description must 229 | be placed in a text file included with all distributions of the Covered 230 | Software under this License. Except to the extent prohibited by statute 231 | or regulation, such description must be sufficiently detailed for a 232 | recipient of ordinary skill to be able to understand it. 233 | 234 | 5. Termination 235 | -------------- 236 | 237 | 5.1. The rights granted under this License will terminate automatically 238 | if You fail to comply with any of its terms. However, if You become 239 | compliant, then the rights granted under this License from a particular 240 | Contributor are reinstated (a) provisionally, unless and until such 241 | Contributor explicitly and finally terminates Your grants, and (b) on an 242 | ongoing basis, if such Contributor fails to notify You of the 243 | non-compliance by some reasonable means prior to 60 days after You have 244 | come back into compliance. Moreover, Your grants from a particular 245 | Contributor are reinstated on an ongoing basis if such Contributor 246 | notifies You of the non-compliance by some reasonable means, this is the 247 | first time You have received notice of non-compliance with this License 248 | from such Contributor, and You become compliant prior to 30 days after 249 | Your receipt of the notice. 250 | 251 | 5.2. If You initiate litigation against any entity by asserting a patent 252 | infringement claim (excluding declaratory judgment actions, 253 | counter-claims, and cross-claims) alleging that a Contributor Version 254 | directly or indirectly infringes any patent, then the rights granted to 255 | You by any and all Contributors for the Covered Software under Section 256 | 2.1 of this License shall terminate. 257 | 258 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 259 | end user license agreements (excluding distributors and resellers) which 260 | have been validly granted by You or Your distributors under this License 261 | prior to termination shall survive termination. 262 | 263 | ************************************************************************ 264 | * * 265 | * 6. Disclaimer of Warranty * 266 | * ------------------------- * 267 | * * 268 | * Covered Software is provided under this License on an "as is" * 269 | * basis, without warranty of any kind, either expressed, implied, or * 270 | * statutory, including, without limitation, warranties that the * 271 | * Covered Software is free of defects, merchantable, fit for a * 272 | * particular purpose or non-infringing. The entire risk as to the * 273 | * quality and performance of the Covered Software is with You. * 274 | * Should any Covered Software prove defective in any respect, You * 275 | * (not any Contributor) assume the cost of any necessary servicing, * 276 | * repair, or correction. This disclaimer of warranty constitutes an * 277 | * essential part of this License. No use of any Covered Software is * 278 | * authorized under this License except under this disclaimer. * 279 | * * 280 | ************************************************************************ 281 | 282 | ************************************************************************ 283 | * * 284 | * 7. Limitation of Liability * 285 | * -------------------------- * 286 | * * 287 | * Under no circumstances and under no legal theory, whether tort * 288 | * (including negligence), contract, or otherwise, shall any * 289 | * Contributor, or anyone who distributes Covered Software as * 290 | * permitted above, be liable to You for any direct, indirect, * 291 | * special, incidental, or consequential damages of any character * 292 | * including, without limitation, damages for lost profits, loss of * 293 | * goodwill, work stoppage, computer failure or malfunction, or any * 294 | * and all other commercial damages or losses, even if such party * 295 | * shall have been informed of the possibility of such damages. This * 296 | * limitation of liability shall not apply to liability for death or * 297 | * personal injury resulting from such party's negligence to the * 298 | * extent applicable law prohibits such limitation. Some * 299 | * jurisdictions do not allow the exclusion or limitation of * 300 | * incidental or consequential damages, so this exclusion and * 301 | * limitation may not apply to You. * 302 | * * 303 | ************************************************************************ 304 | 305 | 8. Litigation 306 | ------------- 307 | 308 | Any litigation relating to this License may be brought only in the 309 | courts of a jurisdiction where the defendant maintains its principal 310 | place of business and such litigation shall be governed by laws of that 311 | jurisdiction, without reference to its conflict-of-law provisions. 312 | Nothing in this Section shall prevent a party's ability to bring 313 | cross-claims or counter-claims. 314 | 315 | 9. Miscellaneous 316 | ---------------- 317 | 318 | This License represents the complete agreement concerning the subject 319 | matter hereof. If any provision of this License is held to be 320 | unenforceable, such provision shall be reformed only to the extent 321 | necessary to make it enforceable. Any law or regulation which provides 322 | that the language of a contract shall be construed against the drafter 323 | shall not be used to construe this License against a Contributor. 324 | 325 | 10. Versions of the License 326 | --------------------------- 327 | 328 | 10.1. New Versions 329 | 330 | Mozilla Foundation is the license steward. Except as provided in Section 331 | 10.3, no one other than the license steward has the right to modify or 332 | publish new versions of this License. Each version will be given a 333 | distinguishing version number. 334 | 335 | 10.2. Effect of New Versions 336 | 337 | You may distribute the Covered Software under the terms of the version 338 | of the License under which You originally received the Covered Software, 339 | or under the terms of any subsequent version published by the license 340 | steward. 341 | 342 | 10.3. Modified Versions 343 | 344 | If you create software not governed by this License, and you want to 345 | create a new license for such software, you may create and use a 346 | modified version of this License if you rename the license and remove 347 | any references to the name of the license steward (except to note that 348 | such modified license differs from this License). 349 | 350 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 351 | Licenses 352 | 353 | If You choose to distribute Source Code Form that is Incompatible With 354 | Secondary Licenses under the terms of this version of the License, the 355 | notice described in Exhibit B of this License must be attached. 356 | 357 | Exhibit A - Source Code Form License Notice 358 | ------------------------------------------- 359 | 360 | This Source Code Form is subject to the terms of the Mozilla Public 361 | License, v. 2.0. If a copy of the MPL was not distributed with this 362 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 363 | 364 | If it is not possible or desirable to put the notice in a particular 365 | file, then You may include the notice in a location (such as a LICENSE 366 | file in a relevant directory) where a recipient would be likely to look 367 | for such a notice. 368 | 369 | You may add additional accurate notices of copyright ownership. 370 | 371 | Exhibit B - "Incompatible With Secondary Licenses" Notice 372 | --------------------------------------------------------- 373 | 374 | This Source Code Form is "Incompatible With Secondary Licenses", as 375 | defined by the Mozilla Public License, v. 2.0. 376 | --------------------------------------------------------------------------------