Skip to content

Commit

Permalink
Merge branch 'upstream-main' into python313
Browse files Browse the repository at this point in the history
  • Loading branch information
CBonnell committed Oct 14, 2024
2 parents ced08d2 + 45c3d69 commit 93b5f9d
Show file tree
Hide file tree
Showing 28 changed files with 670 additions and 203 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build_and_publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
- name: Test wheel
run: |
export WHEELFILE=`ls -1 dist/*.whl | head -n 1`
python -m venv pkilint-testbed
source pkilint-testbed/bin/activate
pip install "$WHEELFILE[dev]"
pytest
Expand Down
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

All notable changes to this project from version 0.9.3 onwards are documented in this file.

## 0.11.5 - 2024-10-03
## 0.12.0 - 2024-10-02

### New features/enhancements

- Add REST API for linting CRLs (#113 - implemented by @dipaktilekar)
- Add validator to flag HTML entities in subject attribute values (#116)
- Add --document-format CLI flag (#115)

### Fixes

- Remove duplicate registration of GeneralNameIpAddressSyntaxValidator in CRL linter (#103 - found by @zzzsz)
- Amend finding code for CRL reason code validator (#104 - found by @zzzsz)
- Remove duplicate registration of CRL validity period validator, fix positive validity period validator (#106 - found by @zzzsz)

## 0.11.4 - 2024-08-29

### New features/enhancements
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,11 @@ The list of command line linters bundled with pkilint:
Each of the linters share common command line parameters:
| Parameter | Default value | Description |
|-------------------|---------------|----------------------------------------------------------------------------------------------------|
| `-s`/`--severity` | INFO | Sets the severity threshold for findings. Findings that are below this threshold are not reported. |
| `-f`/`--format` | TEXT | Sets the format in which results will be reported. Current options are TEXT, CSV, or JSON. |
| Parameter | Default value | Description |
|---------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `-s`/`--severity` | INFO | Sets the severity threshold for findings. Findings that are below this threshold are not reported. |
| `-f`/`--format` | TEXT | Sets the format in which results will be reported. Current options are TEXT, CSV, or JSON. |
| `--document-format` | DETECT | Sets the expected format of documents. If a document is not in the specified format, then an error is reported and the linter exits. Current options are BASE64, DER, PEM, and DETECT (default). |
Additionally, each linter has ability to lint document (certificate, CRL, OCSP response, etc.) files as well as output the set of validations
which are performed by each linter. When the `validations` sub-command is specified, the set of validations that are performed by the linter
Expand Down
2 changes: 1 addition & 1 deletion VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.11.5
0.12.1
12 changes: 7 additions & 5 deletions pkilint/bin/lint_cabf_serverauth_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
import sys

from pkilint import loader, report, util, finding_filter
from pkilint import loader, report, cli_util, finding_filter
from pkilint.cabf import serverauth
from pkilint.cabf.serverauth import serverauth_constants
from pkilint.pkix import certificate
Expand Down Expand Up @@ -53,9 +53,9 @@ def main(cli_args=None) -> int:
lint_parser.add_argument('-r', '--report-all', action='store_true', help='Report all findings without filtering '
'any PKIX findings that are superseded by CA/Browser Forum requirements')

util.add_certificate_validity_period_start_arg(lint_parser)
cli_util.add_certificate_validity_period_start_arg(lint_parser)

util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)
lint_parser.add_argument('file', type=argparse.FileType('rb'),
help='The certificate to lint'
)
Expand All @@ -73,7 +73,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
cert = loader.load_certificate_file(args.file, args.file.name)
cert = loader.RFC5280CertificateDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load certificate: {e}', file=sys.stderr)
return 1
Expand All @@ -100,7 +102,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == "__main__":
Expand Down
12 changes: 7 additions & 5 deletions pkilint/bin/lint_cabf_smime_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from pyasn1.type.univ import ObjectIdentifier

from pkilint import util, loader, report
from pkilint import cli_util, loader, report
from pkilint.cabf import smime
from pkilint.cabf.smime import smime_constants
from pkilint.pkix import certificate
Expand Down Expand Up @@ -93,9 +93,9 @@ def main(cli_args=None) -> int:
help='Output the type of S/MIME certificate to standard error. This option may be '
'useful when using the --detect, --guess, or --mapping options.')

util.add_certificate_validity_period_start_arg(lint_parser)
cli_util.add_certificate_validity_period_start_arg(lint_parser)

util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)

lint_parser.add_argument('file',
type=argparse.FileType('rb'),
Expand All @@ -119,7 +119,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
cert = loader.load_certificate_file(args.file, args.file.name)
cert = loader.RFC5280CertificateDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load certificate: {e}', file=sys.stderr)
return 1
Expand Down Expand Up @@ -152,7 +154,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == '__main__':
Expand Down
10 changes: 6 additions & 4 deletions pkilint/bin/lint_crl.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
import sys

from pkilint import loader, pkix, report, util
from pkilint import loader, pkix, report, cli_util
from pkilint.cabf import cabf_crl
from pkilint.pkix import crl, name, extension

Expand Down Expand Up @@ -34,7 +34,7 @@ def main(cli_args=None) -> int:

lint_parser = subparsers.add_parser('lint', help='Lint the specified CRL')
_add_args(lint_parser)
util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)

lint_parser.add_argument('file', type=argparse.FileType('rb'),
help='The CRL file to lint'
Expand Down Expand Up @@ -80,7 +80,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
crl_doc = loader.load_crl_file(args.file, args.file.name)
crl_doc = loader.RFC5280CertificateListDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load CRL: {e}', file=sys.stderr)
return 1
Expand All @@ -89,7 +91,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == "__main__":
Expand Down
10 changes: 6 additions & 4 deletions pkilint/bin/lint_etsi_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys

from pkilint import etsi
from pkilint import loader, report, util, finding_filter
from pkilint import loader, report, cli_util, finding_filter
from pkilint.etsi import etsi_constants
from pkilint.pkix import certificate

Expand Down Expand Up @@ -53,7 +53,7 @@ def main(cli_args=None) -> int:
lint_parser.add_argument('-r', '--report-all', action='store_true', help='Report all findings without filtering '
'any findings that are superseded by other requirements')

util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)
lint_parser.add_argument('file', type=argparse.FileType('rb'),
help='The certificate to lint'
)
Expand All @@ -71,7 +71,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
cert = loader.load_certificate_file(args.file, args.file.name)
cert = loader.RFC5280CertificateDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load certificate: {e}', file=sys.stderr)
return 1
Expand All @@ -98,7 +100,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == "__main__":
Expand Down
10 changes: 6 additions & 4 deletions pkilint/bin/lint_ocsp_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys

from pkilint import pkix
from pkilint import util, loader, report
from pkilint import cli_util, loader, report
from pkilint.pkix import extension, name, ocsp


Expand All @@ -15,7 +15,7 @@ def main(cli_args=None) -> int:
subparsers.add_parser('validations', help='Output the set of validations which this linter performs')

lint_parser = subparsers.add_parser('lint', help='Lint the specified OCSP response')
util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)

lint_parser.add_argument('file', type=argparse.FileType('rb'),
help='The OCSP response to lint'
Expand All @@ -38,7 +38,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
ocsp_response = loader.load_ocsp_response_file(args.file, args.file.name)
ocsp_response = loader.RFC6960OCSPResponseDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load OCSP response: {e}', file=sys.stderr)
return 1
Expand All @@ -47,7 +49,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == "__main__":
Expand Down
10 changes: 6 additions & 4 deletions pkilint/bin/lint_pkix_cert.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import sys

from pkilint import loader
from pkilint import report, util
from pkilint import report, cli_util
from pkilint.pkix import certificate, name, extension


Expand All @@ -15,7 +15,7 @@ def main(cli_args=None) -> int:
subparsers.add_parser('validations', help='Output the set of validations which this linter performs')

lint_parser = subparsers.add_parser('lint', help='Lint the specified certificate')
util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)

lint_parser.add_argument('file', type=argparse.FileType('rb'),
help='The certificate to lint'
Expand Down Expand Up @@ -45,7 +45,9 @@ def main(cli_args=None) -> int:
return 0
else:
try:
cert = loader.load_certificate_file(args.file, args.file.name)
cert = loader.RFC5280CertificateDocumentLoader().get_file_loader_func(args.document_format)(
args.file, args.file.name
)
except ValueError as e:
print(f'Failed to load certificate: {e}', file=sys.stderr)
return 1
Expand All @@ -54,7 +56,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == "__main__":
Expand Down
12 changes: 7 additions & 5 deletions pkilint/bin/lint_pkix_signer_signee_cert_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from pyasn1_alt_modules import rfc5280

from pkilint import loader, document, util, validation, pkix, report
from pkilint import loader, document, cli_util, validation, pkix, report
from pkilint.pkix import certificate, name, extension, algorithm
from pkilint.pkix.certificate import certificate_extension, certificate_key

Expand Down Expand Up @@ -70,7 +70,7 @@ def main(cli_args=None) -> int:
subparsers.add_parser('validations', help='Output the set of validations which this linter performs')

lint_parser = subparsers.add_parser('lint', help='Lint the specified issuer and subject certificates')
util.add_standard_args(lint_parser)
cli_util.add_standard_args(lint_parser)

lint_parser.add_argument(dest='issuer', type=argparse.FileType('rb'),
help='The issuer certificate to lint'
Expand All @@ -93,8 +93,10 @@ def main(cli_args=None) -> int:
else:
doc_collection = {}

loader_func = loader.RFC5280CertificateDocumentLoader().get_file_loader_func(args.document_format)

try:
issuer = loader.load_certificate_file(
issuer = loader_func(
args.issuer, args.issuer.name, 'issuer', doc_collection
)
except ValueError as e:
Expand All @@ -104,7 +106,7 @@ def main(cli_args=None) -> int:
doc_collection['issuer'] = issuer

try:
subject = loader.load_certificate_file(
subject = loader_func(
args.subject, args.subject.name, 'subject', doc_collection
)
except ValueError as e:
Expand All @@ -120,7 +122,7 @@ def main(cli_args=None) -> int:

print(args.format(results, args.severity))

return util.clamp_exit_code(report.get_findings_count(results, args.severity))
return cli_util.clamp_exit_code(report.get_findings_count(results, args.severity))


if __name__ == '__main__':
Expand Down
30 changes: 30 additions & 0 deletions pkilint/cabf/cabf_name.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import typing

import unicodedata
import html
from iso3166 import countries_by_alpha2
from pyasn1_alt_modules import rfc5280

Expand Down Expand Up @@ -230,3 +231,32 @@ def validate(self, node):
self.VALIDATION_INSIGNIFICANT_ATTRIUBTE_VALUE_PRESENT,
f'Insignificant attribute value: "{value}"'
)


class HTMLEntitiesValidator(validation.Validator):
"""Validates that attribute values do not contain HTML entities using html.unescape."""

VALIDATION_ATTRIBUTE_VALUE_CONTAINS_HTML_ENTITY = validation.ValidationFinding(
validation.ValidationFindingSeverity.ERROR,
'cabf.name.attribute_value_contains_html_entity'
)

def __init__(self):
super().__init__(
validations=[self.VALIDATION_ATTRIBUTE_VALUE_CONTAINS_HTML_ENTITY],
pdu_class=rfc5280.AttributeTypeAndValue
)

def validate(self, node):
value_str = asn1_util.get_string_value_from_attribute_node(node)

if not value_str:
return

unescaped_value = html.unescape(value_str)

if value_str != unescaped_value:
raise validation.ValidationFindingEncountered(
self.VALIDATION_ATTRIBUTE_VALUE_CONTAINS_HTML_ENTITY,
f'Attribute value contains HTML entity: "{value_str}"'
)
1 change: 1 addition & 0 deletions pkilint/cabf/serverauth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ def create_subject_name_validators() -> List[validation.Validator]:
cabf_name.CabfOrganizationIdentifierAttributeValidator(),
serverauth_name.ServerauthRelativeDistinguishedNameContainsOneElementValidator(),
cabf_name.SignificantAttributeValueValidator(),
cabf_name.HTMLEntitiesValidator(),
]


Expand Down
1 change: 1 addition & 0 deletions pkilint/cabf/serverauth/finding_metadata.csv
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ ERROR,cabf.invalid_subject_organization_identifier_format,The format of the subj
ERROR,cabf.invalid_subject_organization_identifier_registration_scheme,Unrecognized Registration Scheme specified in the subject organizationIdentifier attribute.
ERROR,cabf.invalid_subject_organization_identifier_state_province_for_scheme,A state/province has been specified in the subject organizationIdentifier where the Registration Scheme does not permit one.
ERROR,cabf.invalid_subject_organization_identifier_state_province_format,The specified state/province value does not follow the ISO 3166-2 format.
ERROR,cabf.name.attribute_value_contains_html_entity,"""BR 7.1.4.2.2: validates that attribute values do not contain HTML entities."""
ERROR,cabf.rsa_exponent_prohibited_value,"""For RSA key pairs: the CA SHALL confirm that the value of the public exponent is an odd number equal to 3 or more."""
ERROR,cabf.rsa_modulus_invalid_length,"""For RSA key pairs the CA SHALL: Ensure that the modulus size, when encoded, is at least 2048 bits; and Ensure that the modulus size, in bits, is evenly divisible by 8"""
ERROR,cabf.serverauth.aia_duplicate_location,"Validates that all URI locations in the AIA extension are unique, as per BR 7.1.2.10.3, 7.1.2.7.7, and 7.1.2.8.3."
Expand Down
1 change: 1 addition & 0 deletions pkilint/cabf/smime/finding_metadata.csv
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ ERROR,cabf.invalid_subject_organization_identifier_format,SMBR 7.1.4.2.2 (d),The
ERROR,cabf.invalid_subject_organization_identifier_registration_scheme,SMBR 7.1.4.2.2 (d),The inclusion of a scheme which is not recognized
ERROR,cabf.invalid_subject_organization_identifier_state_province_for_scheme,SMBR 7.1.4.2.2 (d),The inclusion of a state/province value when the scheme does not permit such inclusion
ERROR,cabf.invalid_subject_organization_identifier_state_province_format,SMBR 7.1.4.2.2 (d),"The inclusion of a state/province value that violates "" 2 character ISO 3166?2 identifier for the subdivision of the nation in which the Registration Scheme is operated"""
ERROR,cabf.name.attribute_value_contains_html_entity,SMBR 7.1.4.2.2,"""Validates that attribute values do not contain HTML entities."""
ERROR,cabf.no_http_crldp_uri,SMBR 7.1.2.3 (b),"""At least one uniformResourceIdentifier SHALL have the URI scheme HTTP"""
ERROR,cabf.rsa_exponent_prohibited_value,SMBR 6.1.6,"""For RSA key pairs: the CA SHALL confirm that the value of the public exponent is an odd number equal to 3 or more."""
ERROR,cabf.rsa_modulus_invalid_length,SMBR 6.1.5,"""For RSA key pairs the CA SHALL: Ensure that the modulus size, when encoded, is at least 2048 bits; and Ensure that the modulus size, in bits, is evenly divisible by 8"""
Expand Down
Loading

0 comments on commit 93b5f9d

Please sign in to comment.