Skip to content

Commit

Permalink
Merge branch 'master' into fix_ocsp
Browse files Browse the repository at this point in the history
  • Loading branch information
zakird authored May 24, 2024
2 parents d6ccdfb + 8523152 commit 0c5c9b2
Show file tree
Hide file tree
Showing 23 changed files with 1,745 additions and 2 deletions.
11 changes: 11 additions & 0 deletions v3/lint/registration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,17 @@ func TestRegister(t *testing.T) {
expectNames: []string{"goodLint", egLint.Name},
expectSources: SourceList{egLint.Source, MozillaRootStorePolicy},
},
{
name: "new lint source category",
lint: &Lint{
Name: "sct",
Lint: func() LintInterface { return &mockLint{} },
Source: RFC6962,
},
registry: dupeReg,
expectNames: []string{"goodLint", egLint.Name, "sct"},
expectSources: SourceList{egLint.Source, MozillaRootStorePolicy, RFC6962},
},
}

for _, tc := range testCases {
Expand Down
5 changes: 4 additions & 1 deletion v3/lint/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
RFC5280 LintSource = "RFC5280"
RFC5480 LintSource = "RFC5480"
RFC5891 LintSource = "RFC5891"
RFC6962 LintSource = "RFC6962"
RFC8813 LintSource = "RFC8813"
CABFBaselineRequirements LintSource = "CABF_BR"
CABFSMIMEBaselineRequirements LintSource = "CABF_SMIME_BR"
Expand All @@ -51,7 +52,7 @@ func (s *LintSource) UnmarshalJSON(data []byte) error {
}

switch LintSource(throwAway) {
case RFC5280, RFC5480, RFC5891, CABFBaselineRequirements, CABFEVGuidelines, CABFSMIMEBaselineRequirements, MozillaRootStorePolicy, AppleRootStorePolicy, Community, EtsiEsi:
case RFC5280, RFC5480, RFC5891, CABFBaselineRequirements, CABFEVGuidelines, CABFSMIMEBaselineRequirements, MozillaRootStorePolicy, AppleRootStorePolicy, Community, EtsiEsi, RFC6962:
*s = LintSource(throwAway)
return nil
default:
Expand Down Expand Up @@ -87,6 +88,8 @@ func (s *LintSource) FromString(src string) {
*s = AppleRootStorePolicy
case Community:
*s = Community
case RFC6962:
*s = RFC6962
case EtsiEsi:
*s = EtsiEsi
}
Expand Down
5 changes: 4 additions & 1 deletion v3/lints/cabf_smime_br/lint_commonname_mailbox_validated.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ func (l *commonNameMailboxValidated) CheckApplies(c *x509.Certificate) bool {
}

func (l *commonNameMailboxValidated) Execute(c *x509.Certificate) *lint.LintResult {
commonNames := []string{c.Subject.CommonName}
var commonNames []string
if c.Subject.CommonName != "" {
commonNames = append(commonNames, c.Subject.CommonName)
}
commonNames = append(commonNames, c.Subject.CommonNames...)
for _, cn := range commonNames {
if !util.IsMailboxAddress(cn) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ func TestCommonNameMailboxValidated(t *testing.T) {
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - no commonName attribute present",
InputFilename: "smime/mailbox_validated_common_name_absent.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - valid email in commonName",
InputFilename: "smime/mailbox_validated_common_name_good_email.pem",
Expand Down
119 changes: 119 additions & 0 deletions v3/lints/rfc/lint_cert_ext_invalid_der.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* ZLint Copyright 2024 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

/*
* Contributed by Adriano Santoni <[email protected]>
*/

package rfc

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

"crypto/x509/pkix"
"encoding/asn1"
"fmt"
"math/big"
)

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_cert_ext_invalid_der",
Description: "Checks that the 'critical' flag of extensions is not FALSE when present (as per DER encoding)",
Citation: "RFC 5280 $4.2",
Source: lint.RFC5280,
EffectiveDate: util.RFC5280Date,
},
Lint: NewCertExtensionInvalidDER,
})
}

type certExtensionInvalidDER struct{}

/*
* Modified syntax w/respect to RFC 5280, so we can detect whether
* the critical field is actually present in the DER encoding
*/
type Extension struct {
Id asn1.ObjectIdentifier
// This is either the 'critical' or the 'extnValue' field (see RFC 5280 section 4.1)
// We can discriminate based on tag, since the two fields are of different ASN.1 types
Field2 asn1.RawValue
// If this is present, it can only be the 'extnValue' field
// We need to be able to capture it, but we do not deal with it
Field3 asn1.RawValue `asn1:"optional"`
}

// This is just plain RFC 5280
type Certificate struct {
TbsCertificate TBSCertificate
SignatureAlgorithm pkix.AlgorithmIdentifier
SignatureValue asn1.BitString
}

// Simplified with respect to RFC 5280, as we are not interested in most fields here
type TBSCertificate struct {
Version int `asn1:"optional,explicit,default:0,tag:0"`
SerialNumber *big.Int
SignatureAlgo pkix.AlgorithmIdentifier
Issuer asn1.RawValue
Validity asn1.RawValue
Subject asn1.RawValue
PublicKey asn1.RawValue
IssuerUniqueId asn1.BitString `asn1:"optional,tag:1"`
SubjectUniqueId asn1.BitString `asn1:"optional,tag:2"`
Extensions []Extension `asn1:"omitempty,optional,explicit,tag:3"`
}

func NewCertExtensionInvalidDER() lint.LintInterface {
return &certExtensionInvalidDER{}
}

func (l *certExtensionInvalidDER) CheckApplies(c *x509.Certificate) bool {
// This lint applies to any kind of certificate
return true
}

func (l *certExtensionInvalidDER) Execute(c *x509.Certificate) *lint.LintResult {

// Re-decode certificate based on an ad-hoc target struct
var cert Certificate
_, err := asn1.Unmarshal(c.Raw, &cert)

// This should never happen
if err != nil {
return &lint.LintResult{
Status: lint.Fatal,
Details: "Failed to decode certificate",
}
}

for _, ext := range cert.TbsCertificate.Extensions {
if ext.Field2.Tag == asn1.TagBoolean {
// This is the 'critical' flag
if ext.Field2.Bytes[0] == 0 {
// This a BOOLEAN FALSE
return &lint.LintResult{
Status: lint.Error,
Details: fmt.Sprintf("The %v extension is not properly DER-encoded ('critical' must be absent when FALSE)", ext.Id),
}
}
}
}

return &lint.LintResult{Status: lint.Pass}
}
42 changes: 42 additions & 0 deletions v3/lints/rfc/lint_cert_ext_invalid_der_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* ZLint Copyright 2024 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package rfc

import (
"testing"

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

func TestCertExtensionInvalidDEROK(t *testing.T) {
// Regular certificate in proper DER encoding all over
inputPath := "cert_ext_invalid_der_ok_01.pem"
expected := lint.Pass
out := test.TestLint("e_cert_ext_invalid_der", inputPath)
if out.Status != expected {
t.Errorf("%s: expected %s, got %s", inputPath, expected, out.Status)
}
}

func TestCertExtensionInvalidDERKO(t *testing.T) {
// Certificate with improperly DER-encoded SAN extension
inputPath := "cert_ext_invalid_der_ko_01.pem"
expected := lint.Error
out := test.TestLint("e_cert_ext_invalid_der", inputPath)
if out.Status != expected {
t.Errorf("%s: expected %s, got %s", inputPath, expected, out.Status)
}
}
99 changes: 99 additions & 0 deletions v3/lints/rfc/lint_empty_sct_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* ZLint Copyright 2024 Regents of the University of Michigan
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

/*
* Contributed by Adriano Santoni <[email protected]>
*/

package rfc

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

"encoding/asn1"
)

func init() {
lint.RegisterCertificateLint(&lint.CertificateLint{
LintMetadata: lint.LintMetadata{
Name: "e_empty_sct_list",
Description: "At least one SCT MUST be included in the SignedCertificateTimestampList extension",
Citation: "RFC 6962 section 3.3",
Source: lint.RFC6962,
EffectiveDate: util.RFC6962Date,
},
Lint: NewEmptySCTList,
})
}

type emptySCTList struct{}

func NewEmptySCTList() lint.LintInterface {
return &emptySCTList{}
}

// CheckApplies returns true for any subscriber certificates that are not precertificates
// (i.e. that do not have the CT poison extension defined in RFC 6962)
func (l *emptySCTList) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c) && !util.IsExtInCert(c, util.CtPoisonOID)
}

func (l *emptySCTList) Execute(c *x509.Certificate) *lint.LintResult {

var sctListExtValue []byte

for _, e := range c.Extensions {
if e.Id.Equal(util.TimestampOID) {
sctListExtValue = e.Value
break
}
}

// SCT extension not found, so there is nothing to check
if sctListExtValue == nil {
return &lint.LintResult{Status: lint.Pass}
}

var octetString []byte

_, err := asn1.Unmarshal(sctListExtValue, &octetString)
if err != nil {
// This will probably never happen, as at this point the extension has already been parsed by an upper Zlint layer
return &lint.LintResult{
Status: lint.Fatal,
Details: "Error decoding the SignedCertificateTimestampList extension",
}
}

// Per RFC 5246, the SCT list must begin with a two-bytes length field
if len(octetString) < 2 {
// This will probably never happen, as at this point the extension has already been parsed by an upper Zlint layer
return &lint.LintResult{
Status: lint.Fatal,
Details: "Invalid SCT list encoding (missing length field)",
}
}

// If the SCT list length (first two bytes) is zero, then it's an invalid SCT list per RFC 6962
if octetString[0] == 0 && octetString[1] == 0 {
return &lint.LintResult{
Status: lint.Error,
Details: "At least one SCT MUST be included in the SignedCertificateTimestampList extension",
}
}

return &lint.LintResult{Status: lint.Pass}
}
Loading

0 comments on commit 0c5c9b2

Please sign in to comment.