Skip to content

Commit

Permalink
Refactor _parse methods and use property accessors instead
Browse files Browse the repository at this point in the history
  • Loading branch information
ralphje committed Oct 19, 2024
1 parent e4b354e commit 01c4669
Show file tree
Hide file tree
Showing 7 changed files with 654 additions and 725 deletions.
7 changes: 6 additions & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,17 @@ Release notes
=============
This page contains the most significant changes in Signify between each release.

v0.7.2 (unreleased)
v0.8.0 (unreleased)
-------------------
* Add support (don't crash) for the ``microsoft_spc_siginfo`` OID in the
``SpcIndirectDataContent`` structure, used in signing MSI files. We may improve
support for the attributes in this structure in a future release. Note that MSI files
are not (yet) supported.
* Add support for ``SpcRelaxedPeMarkerCheck`` and ``PlatformManifestBinaryID`` as
SignerInfo attributes, although their exact purpose is currently unknown.
* Refactor classes to store the ASN.1 object in the property ``asn1``, and use
property methods as data accessors, instead of assigning all attributes during class
initialization.

v0.7.1 (2024-09-11)
-------------------
Expand Down
294 changes: 143 additions & 151 deletions signify/authenticode/authroot.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import datetime
import hashlib
import pathlib
from typing import Any, Iterable
from typing import Any, Iterable, cast

import mscerts
from asn1crypto import cms
Expand Down Expand Up @@ -47,68 +47,65 @@ class CertificateTrustList(signeddata.SignedData):
}
SubjectIdentifier ::= OCTETSTRING
.. attribute:: data
The underlying ASN.1 data object
.. attribute:: subject_usage
Defines the EKU of the Certificate Trust List. Should be 1.3.6.1.4.1.311.20.1.
.. attribute:: list_identifier
The name of the template of the list.
.. attribute:: sequence_number
The unique number of this list
.. attribute:: this_update
The date of the current CTL.
.. warning::
.. attribute:: next_update
The CTL itself is currently not verifiable.
The date of the next CTL.
"""

.. attribute:: subject_algorithm
_expected_content_type = "microsoft_ctl"
content_asn1: asn1.ctl.CertificateTrustList

Digest algorithm of verifying the list.
@property
def subject_usage(self) -> list[str]:
"""Defines the EKU of the Certificate Trust List.
Should be 1.3.6.1.4.1.311.20.1.
"""
return cast(list[str], self.content_asn1["subject_usage"].native)

.. warning::
@property
def list_identifier(self) -> bytes | None:
"""The name of the template of the list."""
return cast("bytes | None", self.content_asn1["list_identifier"].native)

The CTL itself is currently not verifiable.
@property
def sequence_number(self) -> int:
"""The unique number of this list"""
return cast(int, self.content_asn1["sequence_number"].native)

"""
@property
def this_update(self) -> datetime.datetime | None:
"""The date of the current CTL."""
return cast(
"datetime.datetime | None", self.content_asn1["ctl_this_update"].native
)

_expected_content_type = "microsoft_ctl"
@property
def next_update(self) -> datetime.datetime | None:
"""The date of the next CTL."""
return cast(
"datetime.datetime | None", self.content_asn1["ctl_next_update"].native
)

subject_usage: list[str]
list_identifier: bytes | None
sequence_number: int
this_update: datetime.datetime | None
next_update: datetime.datetime | None
subject_algorithm: HashFunction

def _parse(self) -> None:
super()._parse()

self.subject_usage = self.content["subject_usage"].native
self.list_identifier = self.content["list_identifier"].native
self.sequence_number = self.content["sequence_number"].native
self.this_update = self.content["ctl_this_update"].native
self.next_update = self.content["ctl_next_update"].native
self.subject_algorithm = _get_digest_algorithm(
self.content["subject_algorithm"],
@property
def subject_algorithm(self) -> HashFunction:
"""Digest algorithm of verifying the list."""
return _get_digest_algorithm(
self.content_asn1["subject_algorithm"],
location="CertificateTrustList.subjectAlgorithm",
)
self._subjects = {}
for subj in (
CertificateTrustSubject(subject)
for subject in self.content["trusted_subjects"]
):
self._subjects[subj.identifier.hex().lower()] = subj
# TODO: extensions??

@property
def _subjects(self) -> dict[str, CertificateTrustSubject]:
return {
subj.identifier.hex().lower(): subj
for subj in (
CertificateTrustSubject(subject)
for subject in self.content_asn1["trusted_subjects"]
)
}

# TODO: extensions??

@property
def subjects(self) -> Iterable[CertificateTrustSubject]:
Expand Down Expand Up @@ -176,67 +173,6 @@ class CertificateTrustSubject:
We do not pretend to have a complete picture of all the edge-cases that are
considered.
.. attribute:: data
The underlying ASN.1 data object
.. attribute:: attributes
A dictionary mapping of attribute types to values.
The following values are extracted from the attributes:
.. attribute:: extended_key_usages
Defines the EKU's the certificate is valid for. It may be empty, which we take
as 'all is acceptable'.
.. attribute:: friendly_name
The friendly name of the certificate.
.. attribute:: key_identifier
The sha1 fingerprint of the certificate.
.. attribute:: subject_name_md5
The md5 of the subject name.
.. attribute:: auth_root_sha256
The sha256 fingerprint of the certificate.
.. attribute:: disallowed_filetime
The time since when a certificate has been disabled. Digital signatures with a
timestamp prior to this date continue to be valid, but use cases after this date
are prohibited. It may be used in conjunction with
:attr:`disallowed_extended_key_usages` to define specific EKU's to be disabled.
.. attribute:: root_program_chain_policies
A list of EKU's probably used for EV certificates.
.. attribute:: disallowed_extended_key_usages
Defines the EKU's the certificate is not valid for. When used in combination with
:attr:`disallowed_filetime`, the disabled EKU's are only disabled from that date
onwards, otherwise, it means since the beginning of time.
.. attribute:: not_before_filetime
The time since when new certificates from this CA are not trusted. Certificates
from prior the date will continue to validate. When used in conjunction with
:attr:`not_before_extended_key_usages`, this only concerns certificates issued
after this date for the defined EKU's.
.. attribute:: not_before_extended_key_usages
Defines the EKU's for which the :attr:`not_before_filetime` is considered. If
that attribute is not defined, we assume that it means since the beginning of
time.
.. warning::
The interpretation of the various attributes and their implications has been
Expand All @@ -245,54 +181,110 @@ class CertificateTrustSubject:
"""

extended_key_usages: list[str] | None
friendly_name: str | None
key_identifier: bytes
subject_name_md5: bytes
auth_root_sha256: bytes
disallowed_filetime: datetime.datetime | None
root_program_chain_policies: list[str] | None
disallowed_extended_key_usages: list[str] | None
not_before_filetime: datetime.datetime | None
not_before_extended_key_usages: list[str] | None

def __init__(self, data: asn1.ctl.TrustedSubject):
self.data = data
self._parse()

def _parse(self) -> None:
self.identifier = self.data["subject_identifier"].native
self.attributes = {
def __init__(self, asn1: asn1.ctl.TrustedSubject):
self.asn1 = asn1

@property
def identifier(self) -> bytes:
return cast(bytes, self.asn1["subject_identifier"].native)

@property
def attributes(self) -> dict[str, Any]:
"""A dictionary mapping of attribute types to values."""
return {
attr["type"].native: attr["values"].native[0]
for attr in self.data["subject_attributes"]
for attr in self.asn1["subject_attributes"]
}

self.extended_key_usages = self.attributes.get(
"microsoft_ctl_enhkey_usage", None
@property
def extended_key_usages(self) -> list[str] | None:
"""Defines the EKU's the certificate is valid for. It may be empty, which we
take as 'all is acceptable'.
"""
return cast(
"list[str] | None", self.attributes.get("microsoft_ctl_enhkey_usage", None)
)
self.friendly_name = self.attributes.get("microsoft_ctl_friendly_name", None)
self.key_identifier = self.attributes.get("microsoft_ctl_key_identifier", b"")
self.subject_name_md5 = self.attributes.get(
"microsoft_ctl_subject_name_md5_hash", b""

@property
def friendly_name(self) -> str | None:
"""The friendly name of the certificate."""
return cast(
"str | None", self.attributes.get("microsoft_ctl_friendly_name", None)
)
# TODO: RootProgramCertPolicies not implemented
self.auth_root_sha256 = self.attributes.get(
"microsoft_ctl_auth_root_sha256_hash", b""

@property
def key_identifier(self) -> bytes:
"""The sha1 fingerprint of the certificate."""
return cast(bytes, self.attributes.get("microsoft_ctl_key_identifier", b""))

@property
def subject_name_md5(self) -> bytes:
"""The md5 of the subject name."""
return cast(
bytes, self.attributes.get("microsoft_ctl_subject_name_md5_hash", b"")
)
self.disallowed_filetime = self.attributes.get(
"microsoft_ctl_disallowed_filetime", None

# TODO: RootProgramCertPolicies not implemented

@property
def auth_root_sha256(self) -> bytes:
"""The sha256 fingerprint of the certificate."""
return cast(
bytes, self.attributes.get("microsoft_ctl_auth_root_sha256_hash", b"")
)
self.root_program_chain_policies = self.attributes.get(
"microsoft_ctl_root_program_chain_policies", None

@property
def disallowed_filetime(self) -> datetime.datetime | None:
"""The time since when a certificate has been disabled. Digital signatures with
a timestamp prior to this date continue to be valid, but use cases after this
date are prohibited. It may be used in conjunction with
:attr:`disallowed_extended_key_usages` to define specific EKU's to be disabled.
"""
return cast(
"datetime.datetime | None",
self.attributes.get("microsoft_ctl_disallowed_filetime", None),
)
self.disallowed_extended_key_usages = self.attributes.get(
"microsoft_ctl_disallowed_enhkey_usage", None

@property
def root_program_chain_policies(self) -> list[str] | None:
"""A list of EKU's probably used for EV certificates."""
return cast(
"list[str] | None",
self.attributes.get("microsoft_ctl_root_program_chain_policies", None),
)
self.not_before_filetime = self.attributes.get(
"microsoft_ctl_not_before_filetime", None

@property
def disallowed_extended_key_usages(self) -> list[str] | None:
"""Defines the EKU's the certificate is not valid for. When used in combination
with :attr:`disallowed_filetime`, the disabled EKU's are only disabled from
that date onwards, otherwise, it means since the beginning of time.
"""
return cast(
"list[str] | None",
self.attributes.get("microsoft_ctl_disallowed_enhkey_usage", None),
)

@property
def not_before_filetime(self) -> datetime.datetime | None:
"""The time since when new certificates from this CA are not trusted.
Certificates from prior the date will continue to validate. When used in
conjunction with :attr:`not_before_extended_key_usages`, this only concerns
certificates issued after this date for the defined EKU's.
"""
return cast(
"datetime.datetime | None",
self.attributes.get("microsoft_ctl_not_before_filetime", None),
)
self.not_before_extended_key_usages = self.attributes.get(
"microsoft_ctl_not_before_enhkey_usage", None

@property
def not_before_extended_key_usages(self) -> list[str] | None:
"""Defines the EKU's for which the :attr:`not_before_filetime` is considered. If
that attribute is not defined, we assume that it means since the beginning of
time.
"""
return cast(
"list[str] | None",
self.attributes.get("microsoft_ctl_not_before_enhkey_usage", None),
)

def verify_trust(
Expand Down
Loading

0 comments on commit 01c4669

Please sign in to comment.