Skip to content

Commit

Permalink
qcp-n-qscd: 412-2 check for legal person issuers by re-using 412_3 code
Browse files Browse the repository at this point in the history
  • Loading branch information
breynders-cb committed Nov 1, 2024
1 parent 8fbe5ce commit f54f3e6
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 81 deletions.
3 changes: 2 additions & 1 deletion pkilint/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ def __init__(
attribute_allowances,
finding_code_classifier: str,
unknown_attribute_allowance: Rfc2119Word,
path: str = "certificate.tbsCertificate.subject.rdnSequence",
):
unexpected_attribute_finding = (
None
Expand All @@ -339,5 +340,5 @@ def __init__(
finding_code_classifier + ".{oid}_attribute_present",
finding_code_classifier + ".{oid}_attribute_absent",
unexpected_attribute_finding,
path="certificate.tbsCertificate.subject.rdnSequence",
path=path,
)
14 changes: 13 additions & 1 deletion pkilint/etsi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,8 @@ def create_validators(
if additional_name_validators:
subject_validators.extend(additional_name_validators)

issuer_validators = []

qc_statement_validators = [
ts_119_495.RolesOfPspValidator(),
ts_119_495.NCANameLatinCharactersValidator(),
Expand Down Expand Up @@ -315,6 +317,16 @@ def create_validators(
en_319_412_2.NaturalPersonSubjectAttributeAllowanceValidator()
)

if certificate_type in etsi_constants.EU:
issuer_validators.extend(
[
en_319_412_2.LegalPersonIssuerCountryCodeValidator(),
en_319_412_2.LegalPersonIssuerOrganizationAttributesEqualityValidator(),
en_319_412_2.LegalPersonIssuerDuplicateAttributeAllowanceValidator(),
en_319_412_2.LegalPersonIssuerAttributeAllowanceValidator(),
]
)

if certificate_type not in etsi_constants.CABF_CERTIFICATE_TYPES:
extension_validators.extend(
[
Expand Down Expand Up @@ -410,7 +422,7 @@ def create_validators(
)

return [
certificate.create_issuer_validator_container([]),
certificate.create_issuer_validator_container(issuer_validators),
certificate.create_validity_validator_container(
additional_validity_validators
),
Expand Down
63 changes: 61 additions & 2 deletions pkilint/etsi/en_319_412_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
from pyasn1.type import univ
from pyasn1_alt_modules import rfc5280, rfc3739

import pkilint.etsi.asn1.en_319_411_2
import pkilint.etsi.en_319_412_3
from pkilint import validation, oid, document, common
from pkilint.etsi import asn1 as etsi_asn1, etsi_shared
from pkilint.etsi import etsi_constants
from pkilint.etsi import etsi_shared
from pkilint.etsi.asn1 import en_319_411_2
from pkilint.pkix import extension, name, Rfc2119Word
from pkilint.pkix.general_name import GeneralNameTypeName
Expand Down Expand Up @@ -565,3 +565,62 @@ class ExtensionsPresenceValidator(common.ExtensionsPresenceValidator):

def __init__(self):
super().__init__(self.VALIDATION_EXTENSIONS_FIELD_ABSENT)


_LEGAL_PERSON_REQUIRED_ATTRIBUTES = {
rfc5280.id_at_countryName,
rfc5280.id_at_organizationName,
rfc5280.id_at_commonName,
}


class LegalPersonIssuerAttributeAllowanceValidator(
etsi_shared.LegalPersonAttributeAllowanceValidator
):
_CODE_CLASSIFIER = "etsi.en_319_412_2.GEN-4.2.3.1-2"

def __init__(self):
super().__init__(
self._CODE_CLASSIFIER,
_LEGAL_PERSON_REQUIRED_ATTRIBUTES,
"certificate.tbsCertificate.issuer.rdnSequence",
)


class LegalPersonIssuerDuplicateAttributeAllowanceValidator(
etsi_shared.LegalPersonDuplicateAttributeAllowanceValidator
):
VALIDATION_PROHIBITED_DUPLICATE_ATTRIBUTE_PRESENT = validation.ValidationFinding(
validation.ValidationFindingSeverity.ERROR,
"etsi.en_319_412_2.GEN-4.2.3.1-5.prohibited_duplicate_attribute_present",
)

def __init__(self):
super().__init__(
self.VALIDATION_PROHIBITED_DUPLICATE_ATTRIBUTE_PRESENT,
_LEGAL_PERSON_REQUIRED_ATTRIBUTES,
)


class LegalPersonIssuerOrganizationAttributesEqualityValidator(
etsi_shared.LegalPersonOrganizationAttributesEqualityValidator
):
VALIDATION_ORGID_ORGNAME_ATTRIBUTE_VALUES_EQUAL = validation.ValidationFinding(
validation.ValidationFindingSeverity.ERROR,
"etsi.en_319_412_2.GEN-4.2.3.1-3.organization_id_and_organization_name_attribute_values_equal",
)

def __init__(self):
super().__init__(self.VALIDATION_ORGID_ORGNAME_ATTRIBUTE_VALUES_EQUAL)


class LegalPersonIssuerCountryCodeValidator(
etsi_shared.LegalPersonCountryCodeValidator
):
VALIDATION_UNKNOWN_COUNTRY_CODE = validation.ValidationFinding(
validation.ValidationFindingSeverity.NOTICE,
"etsi.en_319_412_2.GEN-4.2.3.1-6.unknown_country_code",
)

def __init__(self):
super().__init__(self.VALIDATION_UNKNOWN_COUNTRY_CODE)
92 changes: 17 additions & 75 deletions pkilint/etsi/en_319_412_3.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from pyasn1_alt_modules import rfc5280

from pkilint import common
from pkilint import validation
from pkilint.common import organization_id
from pkilint.etsi import etsi_shared
from pkilint.itu import x520_name, asn1_util
from pkilint.pkix import Rfc2119Word, name
from pkilint.itu import x520_name

_REQUIRED_ATTRIBUTES = {
_LEGAL_PERSON_REQUIRED_ATTRIBUTES = {
rfc5280.id_at_countryName,
rfc5280.id_at_organizationName,
x520_name.id_at_organizationIdentifier,
Expand All @@ -16,7 +13,7 @@


class LegalPersonSubjectAttributeAllowanceValidator(
common.AttributeIdentifierAllowanceValidator
etsi_shared.LegalPersonAttributeAllowanceValidator
):
"""
LEG-4.2.1-2: The subject field shall include at least the following attributes as specified in Recommendation
Expand All @@ -25,15 +22,17 @@ class LegalPersonSubjectAttributeAllowanceValidator(

_CODE_CLASSIFIER = "etsi.en_319_412_3.leg-4.2.1-2"

_ATTRIBUTE_ALLOWANCES = {a: Rfc2119Word.MUST for a in _REQUIRED_ATTRIBUTES}

def __init__(self):
super().__init__(
self._ATTRIBUTE_ALLOWANCES, self._CODE_CLASSIFIER, Rfc2119Word.MAY
self._CODE_CLASSIFIER,
_LEGAL_PERSON_REQUIRED_ATTRIBUTES,
"certificate.tbsCertificate.subject.rdnSequence",
)


class LegalPersonDuplicateAttributeAllowanceValidator(validation.Validator):
class LegalPersonDuplicateAttributeAllowanceValidator(
etsi_shared.LegalPersonDuplicateAttributeAllowanceValidator
):
"""
LEG-4.2.1-3: Only one instance of each of these attributes shall be present.
"""
Expand All @@ -45,22 +44,14 @@ class LegalPersonDuplicateAttributeAllowanceValidator(validation.Validator):

def __init__(self):
super().__init__(
validations=[self.VALIDATION_PROHIBITED_DUPLICATE_ATTRIBUTE_PRESENT],
pdu_class=rfc5280.Name,
self.VALIDATION_PROHIBITED_DUPLICATE_ATTRIBUTE_PRESENT,
_LEGAL_PERSON_REQUIRED_ATTRIBUTES,
)

def validate(self, node):
attr_counts = name.get_name_attribute_counts(node)

for a in _REQUIRED_ATTRIBUTES:
if attr_counts[a] > 1:
raise validation.ValidationFindingEncountered(
self.VALIDATION_PROHIBITED_DUPLICATE_ATTRIBUTE_PRESENT,
f"Prohibited duplicate attribute present: {a}",
)


class LegalPersonOrganizationAttributesEqualityValidator(validation.Validator):
class LegalPersonOrganizationAttributesEqualityValidator(
etsi_shared.LegalPersonOrganizationAttributesEqualityValidator
):
"""
LEG-4.2.1-6: The organizationIdentifier attribute shall contain an identification of the subject organization
different from the organization name.
Expand All @@ -72,44 +63,7 @@ class LegalPersonOrganizationAttributesEqualityValidator(validation.Validator):
)

def __init__(self):
super().__init__(
validations=[self.VALIDATION_ORGID_ORGNAME_ATTRIBUTE_VALUES_EQUAL],
pdu_class=rfc5280.Name,
)

def validate(self, node):
# only get the first instance of the attributes
orgname_attr_and_idx = next(
iter(
name.get_name_attributes_by_type(node, rfc5280.id_at_organizationName)
),
None,
)
orgid_attr_and_idx = next(
iter(
name.get_name_attributes_by_type(
node, x520_name.id_at_organizationIdentifier
)
),
None,
)

if orgname_attr_and_idx and orgid_attr_and_idx:
orgname_attr, _ = orgname_attr_and_idx
orgid_attr, _ = orgid_attr_and_idx

orgname = asn1_util.get_string_value_from_attribute_node(orgname_attr)
orgid = asn1_util.get_string_value_from_attribute_node(orgid_attr)

# if any of the attributes were not decoded, then return early
if orgname is None or orgid is None:
return

if orgname.casefold() == orgid.casefold():
raise validation.ValidationFindingEncountered(
self.VALIDATION_ORGID_ORGNAME_ATTRIBUTE_VALUES_EQUAL,
f'Organization name and identifier attribute values are equal: "{orgname}"',
)
super().__init__(self.VALIDATION_ORGID_ORGNAME_ATTRIBUTE_VALUES_EQUAL)


class LegalPersonKeyUsageValidator(etsi_shared.KeyUsageValidator):
Expand Down Expand Up @@ -139,7 +93,7 @@ def __init__(self, is_content_commitment_type):
)


class LegalPersonCountryCodeValidator(validation.Validator):
class LegalPersonCountryCodeValidator(etsi_shared.LegalPersonCountryCodeValidator):
"""
LEG-4.2.1-4: The countryName attribute shall specify the country in which the subject (legal person) is established.
"""
Expand All @@ -150,16 +104,4 @@ class LegalPersonCountryCodeValidator(validation.Validator):
)

def __init__(self):
super().__init__(
validations=[self.VALIDATION_UNKNOWN_COUNTRY_CODE],
pdu_class=rfc5280.X520countryName,
)

def validate(self, node):
value_str = str(node.pdu)

if value_str not in organization_id.ISO3166_1_COUNTRY_CODES:
raise validation.ValidationFindingEncountered(
self.VALIDATION_UNKNOWN_COUNTRY_CODE,
f'Unknown country code: "{value_str}"',
)
super().__init__(self.VALIDATION_UNKNOWN_COUNTRY_CODE)
114 changes: 112 additions & 2 deletions pkilint/etsi/etsi_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

from pyasn1_alt_modules import rfc5280

from pkilint import validation, document
from pkilint.itu import bitstring
from pkilint import validation, document, common
from pkilint.common import organization_id
from pkilint.itu import bitstring, x520_name, asn1_util
from pkilint.pkix import Rfc2119Word, name
from pkilint.pkix.certificate.certificate_extension import KeyUsageBitName


Expand Down Expand Up @@ -147,3 +149,111 @@ def validate(self, node):
VALIDATION_INTERNAL_IP_ADDRESS = validation.ValidationFinding(
validation.ValidationFindingSeverity.ERROR, "etsi.internal_ip_address"
)


class LegalPersonAttributeAllowanceValidator(
common.AttributeIdentifierAllowanceValidator
):

def __init__(self, code_classifier, required_attributes, path_to_rdnSequence):
_ATTRIBUTE_ALLOWANCES = {a: Rfc2119Word.MUST for a in required_attributes}
super().__init__(
_ATTRIBUTE_ALLOWANCES,
code_classifier,
Rfc2119Word.MAY,
path=path_to_rdnSequence,
)


class LegalPersonDuplicateAttributeAllowanceValidator(validation.Validator):
"""
412-3 LEG-4.2.1-3 and 412-2 GEN-4.2.3.1-5: Only one instance of each of these attributes shall be present.
"""

def __init__(self, finding, required_attributes):
self._finding = finding
self._required_attributes = required_attributes
super().__init__(
validations=[finding],
pdu_class=rfc5280.Name,
)

def validate(self, node):
attr_counts = name.get_name_attribute_counts(node)

for a in self._required_attributes:
if attr_counts[a] > 1:
raise validation.ValidationFindingEncountered(
self._finding,
f"Prohibited duplicate attribute present: {a}",
)


class LegalPersonOrganizationAttributesEqualityValidator(validation.Validator):
"""
412-3 LEG-4.2.1-6 and 412-2 GEN-4.2.3.1-8: The organizationIdentifier attribute shall contain an identification of the subject organization
different from the organization name.
"""

def __init__(self, finding):
self._finding = finding
super().__init__(
validations=[finding],
pdu_class=rfc5280.Name,
)

def validate(self, node):
# only get the first instance of the attributes
orgname_attr_and_idx = next(
iter(
name.get_name_attributes_by_type(node, rfc5280.id_at_organizationName)
),
None,
)
orgid_attr_and_idx = next(
iter(
name.get_name_attributes_by_type(
node, x520_name.id_at_organizationIdentifier
)
),
None,
)

if orgname_attr_and_idx and orgid_attr_and_idx:
orgname_attr, _ = orgname_attr_and_idx
orgid_attr, _ = orgid_attr_and_idx

orgname = asn1_util.get_string_value_from_attribute_node(orgname_attr)
orgid = asn1_util.get_string_value_from_attribute_node(orgid_attr)

# if any of the attributes were not decoded, then return early
if orgname is None or orgid is None:
return

if orgname.casefold() == orgid.casefold():
raise validation.ValidationFindingEncountered(
self._finding,
f'Organization name and identifier attribute values are equal: "{orgname}"',
)


class LegalPersonCountryCodeValidator(validation.Validator):
"""
412-3 LEG-4.2.1-4 and 412-2 GEN-4.2.3.1-6: The countryName attribute shall specify the country in which the subject (legal person) is established.
"""

def __init__(self, finding):
self._finding = finding
super().__init__(
validations=[finding],
pdu_class=rfc5280.X520countryName,
)

def validate(self, node):
value_str = str(node.pdu)

if value_str not in organization_id.ISO3166_1_COUNTRY_CODES:
raise validation.ValidationFindingEncountered(
self._finding,
f'Unknown country code: "{value_str}"',
)
Loading

0 comments on commit f54f3e6

Please sign in to comment.