PKCS#7: SignedData and SignerInfo

To support Authenticode, this library includes some code to parse and validate SignedData structures. These are defined in several RFCs, which used to be called PKCS#7 The structure of all relevant RFC’s follow ASN.1 notation to define the relevant structures. These definitions are not always easily digested, but it does show which fields are available.

This chapter of the documentation shows how these basic structures work, so we can dive deep into their operations in the next chapter.

The following diagram shows the relation between SignedData and SignerInfo:

https://yuml.me/8e9c7bb6.svg

Note that although this diagram is not very complicated, when discussing Authenticode, we will be creating multiple SignedData and SignerInfo structures, nested in each other, so it’s important to fully understand this structure.

SignedData

The SignedData object is the root structure for sending encrypted data in PKCS#7.

class signify.pkcs7.SignedData(asn1: SignedData)

A generic SignedData object. The SignedData object is defined in RFC2315 and RFC5652 (amongst others) and defines data that is signed by one or more signers.

It is based on the following ASN.1 object (as per RFC2315):

SignedData ::= SEQUENCE {
  version Version,
  digestAlgorithms DigestAlgorithmIdentifiers,
  contentInfo ContentInfo,
  certificates [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL,
  crls [1] IMPLICIT CertificateRevocationLists OPTIONAL,
  signerInfos SignerInfos
}

In general, it describes some form of data, that is (obviously) signed. In the ASN.1 structure, you see the contentInfo, which describes the signed content. (See content_type and content_asn1.

Each SignedData object may contain multiple signers. Information about these is found in signer_infos, pointing to one or more SignerInfo classes.

Additionally, certificates contains any additional (intermediate) certificates that may be required to verify these signers.

property certificates: CertificateStore

A list of all included certificates in the SignedData. These can be used to determine a valid validation path from the signer to a root certificate.

property content_asn1: Asn1Value

The actual content, as parsed by the content_type spec.

property content_type: str

The class of the type of the content in the object.

property digest_algorithm: HashFunction

The digest algorithm, i.e. the hash algorithm, that is used by the signers of the data.

classmethod from_envelope(data: bytes, *args: Any, **kwargs: Any) Self

Loads a SignedData object from raw data that contains ContentInfo.

Parameters:

data – The bytes to parse

get_content_digest() bytes

Returns the actual digest of the content of the SignedData object, adhering to the specs in RFC2315, 9.3; the identifier (tag) and length need to be stripped for hashing.

property signer_info: SignerInfo

The included signer_info.SignerInfo object, if there’s one.

property signer_infos: Sequence[SignerInfo]

A list of all included signer_info.SignerInfo objects

verify(verification_context: VerificationContext | None = None, *, cs_verification_context: VerificationContext | None = None, trusted_certificate_store: CertificateStore | None = None, extended_key_usages: list[str] | None = None, verification_context_kwargs: dict[str, Any] | None = None, countersignature_mode: Literal['strict', 'permit', 'ignore'] = 'strict') Iterable[list[Certificate]]

Verifies the SignedData structure:

  • Verifies that the content, when hashed, is the same as the value in SignerInfo.message_digest

  • In the case of a countersigner, calls check_message_digest() on the countersigner to verify that the hashed value of SignerInfo.encrypted_digest is contained in the countersigner.

  • Verifies the chain of the countersigner up to a trusted root, see SignerInfo.verify() and RFC3161SignedData.verify()

  • Verifies the chain of the signer up to a trusted root, see SignerInfo.verify()

In the case of a countersigner, the verification is performed using the timestamp of the CounterSignerInfo, otherwise now is assumed. If there is no countersigner, you can override this by specifying a different timestamp in the VerificationContext. Note that you cannot set a timestamp when checking against the CRL; this is not permitted by the underlying library. If you need to do this, you must therefore set countersignature_mode to ignore.

Parameters:
  • verification_context – The VerificationContext for verifying the chain of the SignerInfo. The timestamp is overridden in the case of a countersigner. Default stores are trusted_certificate_store and the certificates of this SignedData object. Required EKU is provided as extended_key_usages

  • cs_verification_context – The VerificationContext for verifying the chain of the CounterSignerInfo. The timestamp is overridden in the case of a countersigner. Default stores are trusted_certificate_store and the certificates of this SignedData object. Required EKU is time_stamping.

  • trusted_certificate_store – A CertificateStore object that contains a list of trusted certificates to be used when None is passed to either verification_context or cs_verification_context and a VerificationContext is created.

  • extended_key_usages – EKU’s to check for in the verification context of this SignedData object.

  • verification_context_kwargs (dict) – If provided, keyword arguments that are passed to the instantiation of VerificationContext s created in this function. Used for e.g. providing a timestamp.

  • countersignature_mode (str) – Changes how countersignatures are handled. Defaults to ‘strict’, which means that errors in the countersignature result in verification failure. If set to ‘permit’, the countersignature is checked, but when it errors, it is verified as if the countersignature was never set. When set to ‘ignore’, countersignatures are never checked.

Raises:

VerificationError – when the verification failed

Returns:

A list of valid certificate chains for this SignedData.

SignerInfo

class signify.pkcs7.SignerInfo(asn1: SignerInfo, parent: SignedData | None = None)

The SignerInfo class is defined in RFC2315 and RFC5652 (amongst others) and defines the per-signer information in a SignedData structure.

It is based on the following ASN.1 object (as per RFC2315):

SignerInfo ::= SEQUENCE {
  version Version,
  issuerAndSerialNumber IssuerAndSerialNumber,
  digestAlgorithm DigestAlgorithmIdentifier,
  authenticatedAttributes [0] IMPLICIT Attributes OPTIONAL,
  digestEncryptionAlgorithm DigestEncryptionAlgorithmIdentifier,
  encryptedDigest EncryptedDigest,
  unauthenticatedAttributes [1] IMPLICIT Attributes OPTIONAL
}

The most important part of this structure are the authenticated attributes. These will at least contain the hash of the content of the SignedData structure. We can verify this hash by hashing the same content using the hash in digest_algorithm

The encrypted_digest contains a signature by the issuer over these authenticated attributes (the authenticated attributes are hashed and verified using the digest_encryption_algorithm). The issuer and serial_number contains a reference to the certificate of the issuer, that is used for this signature.

This class defines how a certain signer, (identified by their issuer)

asn1

The underlying ASN.1 data object

parent

The parent SignedData object (or if other SignerInfos are present, it may be another object)

property authenticated_attributes: dict[str, list[Asn1Value]]

A SignerInfo object can contain both signed and unsigned attributes. These contain additional information about the signature, but also the content type and message digest. The difference between signed and unsigned is that unsigned attributes are not validated.

The type of this attribute is a dictionary. You should not need to access this value directly, rather using one of the attributes listed below.

check_message_digest(data: bytes) bool

Given the data, returns whether the hash_algorithm and message_digest match the data provided.

property content_type: str | None

This is an authenticated attribute, containing the content type of the content being signed.

property countersigner: CounterSignerInfo | None

This is an unauthenticated attribute, containing the countersigner of the SignerInfo.

property digest_algorithm: HashFunction

The digest algorithm, i.e. the hash algorithm, under which the content and the authenticated attributes are signed.

property digest_encryption_algorithm: str

This is the algorithm used for signing the digest with the signer’s key.

property encrypted_digest: bytes

The result of encrypting the message digest and associated information with the signer’s private key.

property issuer: CertificateName

The issuer of the SignerInfo, i.e. the certificate of the signer of the SignedData object.

property message_digest: bytes | None

This is an authenticated attribute, containing the signed digest of the data.

potential_chains(context: VerificationContext) Iterable[list[Certificate]]

Retrieves all potential chains from this SignerInfo instance.

Parameters:

context (VerificationContext) – The context

Returns:

A list of potential certificate chains for this SignerInfo.

property serial_number: int

The serial number as specified by the issuer.

property signing_time: datetime | None

This is an authenticated attribute, containing the timestamp of signing. Note that this should only be present in countersigner objects.

property unauthenticated_attributes: dict[str, list[Asn1Value]]

A SignerInfo object can contain both signed and unsigned attributes. These contain additional information about the signature, but also the content type and message digest. The difference between signed and unsigned is that unsigned attributes are not validated.

The type of this attribute is a dictionary. You should not need to access this value directly, rather using one of the attributes listed below.

verify(context: VerificationContext, *, countersigner_context: VerificationContext | None = None, countersignature_mode: Literal['strict', 'permit', 'ignore'] = 'strict') Iterable[list[Certificate]]

Verifies that this SignerInfo verifies up to a chain with the root of a trusted certificate.

Parameters:
  • context – The context for verifying the SignerInfo.

  • countersigner_context – The VerificationContext for verifying the chain of the CounterSignerInfo.

  • countersignature_mode

    Changes how countersignatures are handled. Defaults to strict, which means that errors in the countersignature result in verification failure.

    If set to permit, the countersignature is checked, but when it errors, it is verified as if the countersignature was never set.

    When set to ignore, countersignatures are never checked.

Returns:

A list of valid certificate chains for this SignerInfo.

Raises:

AuthenticodeVerificationError – When the SignerInfo could not be verified.

CounterSignerInfo

class signify.pkcs7.CounterSignerInfo(asn1: SignerInfo, parent: SignedData | None = None)

A counter-signer provides information about when a SignerInfo was signed. It basically acts as the SignerInfo of the SignerInfo, linking the message digest to the original SignerInfo’s encrypted_digest.

This normally works by sending the digest of the SignerInfo to an external trusted service, that will include a signed time in its response.