Skip to content

Commit

Permalink
Ensure AIA URLs point to public paths (#760)
Browse files Browse the repository at this point in the history
* added lints to check if the aia has likely internal names

* add tests for all aia path combinations

* use Hostname instead of Host to account for ports, triage integration test results and update integration config

* address code review feedback (Fatal->Error, handling for http schemes)

* handle https as well

* enforce http scheme, fix test data

* don't require any OCSPServer to exist

* also don't require IssuingCertificateURLs
  • Loading branch information
cardonator authored Nov 6, 2023
1 parent 8923170 commit 64533b5
Show file tree
Hide file tree
Showing 13 changed files with 633 additions and 0 deletions.
3 changes: 3 additions & 0 deletions v3/integration/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -931,6 +931,9 @@
"w_subject_surname_recommended_max_length": {},
"w_tls_server_cert_valid_time_longer_than_397_days": {
"WarnCount": 223
},
"w_sub_cert_aia_contains_internal_names": {
"WarnCount": 210
}
}
}
77 changes: 77 additions & 0 deletions v3/lints/cabf_br/lint_sub_cert_aia_contains_internal_names.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package cabf_br

/*
* ZLint Copyright 2023 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import (
"net/url"
"time"

"github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

type subCertAIAInternalName struct{}

/************************************************************************
BRs: 7.1.2.10.3
CA Certificate Authority Information Access
This extension MAY be present. If present, it MUST NOT be marked critical, and it MUST contain the
HTTP URL of the CA’s CRL service.
id-ad-ocsp A HTTP URL of the Issuing CA's OCSP responder.
id-ad-caIssuers A HTTP URL of the Issuing CA's Certificate.
*************************************************************************/

func init() {
lint.RegisterLint(&lint.Lint{
Name: "w_sub_cert_aia_contains_internal_names",
Description: "Subscriber certificates authorityInformationAccess extension should contain the HTTP URL of the issuing CA’s certificate, for public certificates this should not be an internal name",
Citation: "BRs: 7.1.2.10.3",
Source: lint.CABFBaselineRequirements,
EffectiveDate: util.CABEffectiveDate,
Lint: NewSubCertAIAInternalName,
})
}

func NewSubCertAIAInternalName() lint.LintInterface {
return &subCertAIAInternalName{}
}

func (l *subCertAIAInternalName) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c)
}

func (l *subCertAIAInternalName) Execute(c *x509.Certificate) *lint.LintResult {
for _, u := range c.OCSPServer {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
}
for _, u := range c.IssuingCertificateURL {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
}
return &lint.LintResult{Status: lint.Pass}
}
35 changes: 35 additions & 0 deletions v3/lints/cabf_br/lint_sub_cert_aia_contains_internal_names_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cabf_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestAIAInternalName(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - aia with valid names",
InputFilename: "aiaWithValidNames.pem",
ExpectedResult: lint.Pass,
},
{
Name: "warn - aia with internal names",
InputFilename: "aiaWithInternalNames.pem",
ExpectedResult: lint.Warn,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("w_sub_cert_aia_contains_internal_names", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
95 changes: 95 additions & 0 deletions v3/lints/cabf_smime_br/lint_legacy_aia_contains_internal_names.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package cabf_smime_br

/*
* ZLint Copyright 2023 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import (
"net/url"
"time"

"github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

type smimeLegacyAIAContainsInternalNames struct{}

/************************************************************************
BRs: 7.1.2.3c
CA Certificate Authority Information Access
The authorityInformationAccess extension MAY contain one or more accessMethod
values for each of the following types:
id-ad-ocsp specifies the URI of the Issuing CA's OCSP responder.
id-ad-caIssuers specifies the URI of the Issuing CA's Certificate.
For Legacy: When provided, at least one accessMethod SHALL have the URI scheme HTTP. Other schemes (LDAP, FTP, ...) MAY be present.
*************************************************************************/

func init() {
lint.RegisterLint(&lint.Lint{
Name: "w_smime_legacy_aia_contains_internal_names",
Description: "SMIME Legacy certificates authorityInformationAccess When provided, at least one accessMethod SHALL have the URI scheme HTTP. Other schemes (LDAP, FTP, ...) MAY be present.",
Citation: "BRs: 7.1.2.3c",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABEffectiveDate,
Lint: NewSMIMELegacyAIAInternalName,
})
}

func NewSMIMELegacyAIAInternalName() lint.LintInterface {
return &smimeLegacyAIAContainsInternalNames{}
}

func (l *smimeLegacyAIAContainsInternalNames) CheckApplies(c *x509.Certificate) bool {
return util.IsLegacySMIMECertificate(c)
}

func (l *smimeLegacyAIAContainsInternalNames) Execute(c *x509.Certificate) *lint.LintResult {
atLeastOneHttp := false
for _, u := range c.OCSPServer {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
if purl.Scheme == "http" {
atLeastOneHttp = true
}
}
if !atLeastOneHttp && len(c.OCSPServer) != 0 {
return &lint.LintResult{Status: lint.Error, Details: "at least one accessMethod MUST have the URI scheme HTTP"}
}

atLeastOneHttp = false
for _, u := range c.IssuingCertificateURL {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
if purl.Scheme == "http" {
atLeastOneHttp = true
}
}
if !atLeastOneHttp && len(c.IssuingCertificateURL) != 0 {
return &lint.LintResult{Status: lint.Error, Details: "at least one accessMethod MUST have the URI scheme HTTP"}
}

return &lint.LintResult{Status: lint.Pass}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cabf_smime_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestSMIMELegacyAIAInternalName(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - cert with SAN",
InputFilename: "smime/aiaWithValidNamesLegacy.pem",
ExpectedResult: lint.Pass,
},
{
Name: "error - cert without SAN",
InputFilename: "smime/aiaWithInternalNamesLegacy.pem",
ExpectedResult: lint.Warn,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("w_smime_legacy_aia_contains_internal_names", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
85 changes: 85 additions & 0 deletions v3/lints/cabf_smime_br/lint_strict_aia_contains_internal_names.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cabf_smime_br

/*
* ZLint Copyright 2023 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import (
"net/url"
"time"

"github.com/zmap/zcrypto/x509"
"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/util"
)

type smimeStrictAIAContainsInternalNames struct{}

/************************************************************************
BRs: 7.1.2.3c
CA Certificate Authority Information Access
The authorityInformationAccess extension MAY contain one or more accessMethod
values for each of the following types:
id-ad-ocsp specifies the URI of the Issuing CA's OCSP responder.
id-ad-caIssuers specifies the URI of the Issuing CA's Certificate.
For Strict and Multipurpose: When provided, every accessMethod SHALL have the URI scheme HTTP. Other schemes SHALL NOT be present.
*************************************************************************/

func init() {
lint.RegisterLint(&lint.Lint{
Name: "w_smime_strict_aia_contains_internal_names",
Description: "SMIME Strict certificates authorityInformationAccess When provided, every accessMethod SHALL have the URI scheme HTTP. Other schemes SHALL NOT be present.",
Citation: "BRs: 7.1.2.3c",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABEffectiveDate,
Lint: NewSMIMEStrictAIAInternalName,
})
}

func NewSMIMEStrictAIAInternalName() lint.LintInterface {
return &smimeStrictAIAContainsInternalNames{}
}

func (l *smimeStrictAIAContainsInternalNames) CheckApplies(c *x509.Certificate) bool {
return util.IsStrictSMIMECertificate(c) || util.IsMultipurposeSMIMECertificate(c)
}

func (l *smimeStrictAIAContainsInternalNames) Execute(c *x509.Certificate) *lint.LintResult {
for _, u := range c.OCSPServer {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if purl.Scheme != "http" {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
}
for _, u := range c.IssuingCertificateURL {
purl, err := url.Parse(u)
if err != nil {
return &lint.LintResult{Status: lint.Error}
}
if purl.Scheme != "http" {
return &lint.LintResult{Status: lint.Error}
}
if !util.HasValidTLD(purl.Hostname(), time.Now()) {
return &lint.LintResult{Status: lint.Warn}
}
}
return &lint.LintResult{Status: lint.Pass}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cabf_smime_br

import (
"testing"

"github.com/zmap/zlint/v3/lint"
"github.com/zmap/zlint/v3/test"
)

func TestSMIMEStrictAIAInternalName(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - aia with valid names",
InputFilename: "smime/aiaWithValidNamesStrict.pem",
ExpectedResult: lint.Pass,
},
{
Name: "warn - aia with internal names",
InputFilename: "smime/aiaWithInternalNamesStrict.pem",
ExpectedResult: lint.Warn,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("w_smime_strict_aia_contains_internal_names", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
Loading

0 comments on commit 64533b5

Please sign in to comment.