Skip to content

Commit

Permalink
CABF SMIME BR 7.1.2.3.e - KeyUsages (#757)
Browse files Browse the repository at this point in the history
* add lints for smime ku presence and criticality, rsa KUs and ECC KUs

* Finish lint for ECDSA key usages.  Add lint for edwards curve key usages

* strict rsa ku lint unit tests

* rename rsa strict ku lint test data to reflect strictness of SMIME policy oid

* add unit tests to smime rsa legacy/multipurpose ku lint

* add unit tests to key usage presence lint.  Fix present/presence typos

* rename key usage critical lint to key usage criticality. unit tests for same

* add unit tests to smime ecdsa key usage lint.  Fix issue in check applies

* add unit tests for smime ed25519 ku lint

* use iota constants for signing, key management and dual use to make rsa and ec ku lints clearer to read

* replace bit mask checks with util.HasKeyUsage calls in smime KU lints

* Refactor RSA and EC SMIME KU lints to cover other KUs without digitalSignature and/or keyAgreement/Encipherment with separate lints.

---------

Co-authored-by: Christopher Henderson <chris@chenderson.org>
  • Loading branch information
robplee and christopher-henderson committed Nov 5, 2023
1 parent f9f30bc commit 8923170
Show file tree
Hide file tree
Showing 55 changed files with 2,871 additions and 0 deletions.
83 changes: 83 additions & 0 deletions v3/lints/cabf_smime_br/lint_ecpublickey_key_usages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* 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.
*/

package cabf_smime_br

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

func init() {
lint.RegisterLint(&lint.Lint{
Name: "e_ecpublickey_key_usages",
Description: "For signing only, bit positions SHALL be set for digitalSignature and MAY be set for nonRepudiation. For key management only, bit positions SHALL be set for keyEncipherment.For dual use, bit positions SHALL be set for digitalSignature and keyEncipherment and MAY be set for nonRepudiation.",
Citation: "7.1.2.3.e",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABF_SMIME_BRs_1_0_0_Date,
Lint: NewECPublicKeyKeyUsages,
})
}

type ecPublicKeyKeyUsages struct{}

func NewECPublicKeyKeyUsages() lint.LintInterface {
return &ecPublicKeyKeyUsages{}
}

func (l *ecPublicKeyKeyUsages) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c) && util.IsSMIMEBRCertificate(c) && util.IsExtInCert(c, util.KeyUsageOID) && c.PublicKeyAlgorithm == x509.ECDSA
}

func (l *ecPublicKeyKeyUsages) Execute(c *x509.Certificate) *lint.LintResult {
const (
signing = iota + 1
keyManagement
dualUsage
)

certType := 0
if util.HasKeyUsage(c, x509.KeyUsageDigitalSignature) {
certType |= signing
}
if util.HasKeyUsage(c, x509.KeyUsageKeyAgreement) {
certType |= keyManagement
}

switch certType {
case signing:
mask := 0x1FF ^ (x509.KeyUsageDigitalSignature | x509.KeyUsageContentCommitment)
if c.KeyUsage&mask != 0 {
return &lint.LintResult{Status: lint.Error}
}

case keyManagement:
mask := 0x1FF ^ (x509.KeyUsageKeyAgreement | x509.KeyUsageEncipherOnly | x509.KeyUsageDecipherOnly)
if c.KeyUsage&mask != 0 {
return &lint.LintResult{Status: lint.Error}
}

case dualUsage:
mask := 0x1FF ^ (x509.KeyUsageDigitalSignature | x509.KeyUsageContentCommitment | x509.KeyUsageKeyAgreement | x509.KeyUsageEncipherOnly | x509.KeyUsageDecipherOnly)
if c.KeyUsage&mask != 0 {
return &lint.LintResult{Status: lint.Error}
}

default:
return &lint.LintResult{Status: lint.NA}
}

return &lint.LintResult{Status: lint.Pass}
}
89 changes: 89 additions & 0 deletions v3/lints/cabf_smime_br/lint_ecpublickey_key_usages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cabf_smime_br

import (
"testing"

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

func TestECPublicKeyKeyUsage(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - cert with digitalSignature KU",
InputFilename: "smime/ec_legacy_digital_signature_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with digitalSignature and contentCommitment KUs",
InputFilename: "smime/ec_multipurpose_digital_signature_content_commitment_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with keyAgreement KU",
InputFilename: "smime/ec_strict_key_agreement_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with keyAgreement and encipherOnly KUs",
InputFilename: "smime/ec_legacy_key_agreement_encipher_only_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with keyAgreement and decipherOnly KUs",
InputFilename: "smime/ec_multipurpose_key_agreement_decipher_only.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with digitalSignature, keyAgreement, contentCommitment, and encipherOnly KUs",
InputFilename: "smime/ec_strict_digital_signature_key_agreement_content_commitment_encipher_only_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with digitalSignature, keyAgreement, contentCommitment, and decipherOnly KUs",
InputFilename: "smime/ec_legacy_digital_signature_key_agreement_content_commitment_decipher_only_ku.pem",
ExpectedResult: lint.Pass,
}, {
Name: "NA - cert without KUs",
InputFilename: "smime/without_subject_alternative_name.pem",
ExpectedResult: lint.NA,
},
{
Name: "NA - Certificate without digitalSignature or keyAgreement KUs",
InputFilename: "smime/ec_strict_cert_sign_ku.pem",
ExpectedResult: lint.NA,
},
{
Name: "NE - certificate with valid KUs dated before 2020-09-01",
InputFilename: "smime/ec_multipurpose_valid_ku_august_2023.pem",
ExpectedResult: lint.NE,
},
{
Name: "Error - Signing Certificate with unexpected KU",
InputFilename: "smime/ec_strict_digital_signature_cert_sign_ku.pem",
ExpectedResult: lint.Error,
},
{
Name: "Error - Key Management Certificate with unexpected KU",
InputFilename: "smime/ec_legacy_key_agreement_cert_sign_ku.pem",
ExpectedResult: lint.Error,
},
{
Name: "Error - Dual Use Certificate with unexpected KU",
InputFilename: "smime/ec_multipurpose_digital_signature_key_agreement_cert_sign_ku.pem",
ExpectedResult: lint.Error,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_ecpublickey_key_usages", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
54 changes: 54 additions & 0 deletions v3/lints/cabf_smime_br/lint_ecpublickey_other_key_usages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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.
*/

package cabf_smime_br

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

func init() {
lint.RegisterLint(&lint.Lint{
Name: "e_ec_other_key_usages",
Description: "Other bit positions SHALL NOT be set.",
Citation: "7.1.2.3.e",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABF_SMIME_BRs_1_0_0_Date,
Lint: NewECOtherKeyUsages,
})
}

type ecOtherKeyUsages struct{}

func NewECOtherKeyUsages() lint.LintInterface {
return &ecOtherKeyUsages{}
}

func (l *ecOtherKeyUsages) CheckApplies(c *x509.Certificate) bool {
return util.IsSubscriberCert(c) && util.IsSMIMEBRCertificate(c) && util.IsExtInCert(c, util.KeyUsageOID) && c.PublicKeyAlgorithm == x509.ECDSA
}

func (l *ecOtherKeyUsages) Execute(c *x509.Certificate) *lint.LintResult {
if !(util.HasKeyUsage(c, x509.KeyUsageDigitalSignature) || util.HasKeyUsage(c, x509.KeyUsageKeyAgreement)) {
if c.KeyUsage != 0 {
return &lint.LintResult{Status: lint.Error}
}

return &lint.LintResult{Status: lint.NA}
}

return &lint.LintResult{Status: lint.Pass}
}
50 changes: 50 additions & 0 deletions v3/lints/cabf_smime_br/lint_ecpublickey_other_key_usages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cabf_smime_br

import (
"testing"

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

func TestECOtherKeyUsages(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - cert with digitalSignature KU",
InputFilename: "smime/ec_legacy_digital_signature_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "NE - certificate with valid KUs dated before 2020-09-01",
InputFilename: "smime/ec_multipurpose_valid_ku_august_2023.pem",
ExpectedResult: lint.NE,
},
{
Name: "NA - cert without KUs",
InputFilename: "smime/without_subject_alternative_name.pem",
ExpectedResult: lint.NA,
},
{
Name: "NA - cert with KU extension but no KU bits set",
InputFilename: "smime/ec_no_key_usages.pem",
ExpectedResult: lint.NA,
},
{
Name: "Error - Certificate with non-zero KUs without digitalSignature or keyEncipherment KUs",
InputFilename: "smime/ec_strict_cert_sign_ku.pem",
ExpectedResult: lint.Error,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_ec_other_key_usages", tc.InputFilename)
if result.Status != tc.ExpectedResult {
t.Errorf("expected result %v was %v - details: %v", tc.ExpectedResult, result.Status, result.Details)
}
})
}
}
56 changes: 56 additions & 0 deletions v3/lints/cabf_smime_br/lint_edwardspublickey_key_usages.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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.
*/

package cabf_smime_br

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

func init() {
lint.RegisterLint(&lint.Lint{
Name: "e_edwardspublickey_key_usages",
Description: "Bit positions SHALL be set for digitalSignature and MAY be set for nonRepudiation.",
Citation: "7.1.2.3.e",
Source: lint.CABFSMIMEBaselineRequirements,
EffectiveDate: util.CABF_SMIME_BRs_1_0_0_Date,
Lint: NewEdwardsPublicKeyKeyUsages,
})
}

type edwardsPublicKeyKeyUsages struct{}

func NewEdwardsPublicKeyKeyUsages() lint.LintInterface {
return &edwardsPublicKeyKeyUsages{}
}

func (l *edwardsPublicKeyKeyUsages) CheckApplies(c *x509.Certificate) bool {
// TODO add support for curve448 certificate linting
return util.IsSubscriberCert(c) && util.IsSMIMEBRCertificate(c) && util.IsExtInCert(c, util.KeyUsageOID) && c.PublicKeyAlgorithm == x509.Ed25519
}

func (l *edwardsPublicKeyKeyUsages) Execute(c *x509.Certificate) *lint.LintResult {
if !util.HasKeyUsage(c, x509.KeyUsageDigitalSignature) {
return &lint.LintResult{Status: lint.Error}
}

mask := 0x1FF ^ (x509.KeyUsageDigitalSignature | x509.KeyUsageContentCommitment)
if c.KeyUsage&mask != 0 {
return &lint.LintResult{Status: lint.Error}
}

return &lint.LintResult{Status: lint.Pass}
}
55 changes: 55 additions & 0 deletions v3/lints/cabf_smime_br/lint_edwardspublickey_key_usages_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package cabf_smime_br

import (
"testing"

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

func TestEdwardsPublicKeyKeyUsages(t *testing.T) {
testCases := []struct {
Name string
InputFilename string
ExpectedResult lint.LintStatus
}{
{
Name: "pass - cert with digitalSignature KU",
InputFilename: "smime/ed25519_legacy_digital_signature_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "pass - cert with digitalSignature and contentCommitment KUs",
InputFilename: "smime/ed25519_multipurpose_digital_signature_content_commitment_ku.pem",
ExpectedResult: lint.Pass,
},
{
Name: "NA - non-SMIME BR cert",
InputFilename: "smime/domainValidatedWithEmailCommonName.pem",
ExpectedResult: lint.NA,
},
{
Name: "NA - RSA cert",
InputFilename: "smime/rsa_strict_digital_signature_ku.pem",
ExpectedResult: lint.NA,
},
{
Name: "NE - certificate with KU extension dated before 2020-09-01",
InputFilename: "smime/ed25519_strict_valid_ku_august_2023.pem",
ExpectedResult: lint.NE,
},
{
Name: "Error - Certificate without digitalSignature KU",
InputFilename: "smime/ed25519_strict_cert_sign_ku.pem",
ExpectedResult: lint.Error,
},
}
for _, tc := range testCases {
t.Run(tc.Name, func(t *testing.T) {
result := test.TestLint("e_edwardspublickey_key_usages", 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 8923170

Please sign in to comment.