168 lines
5.2 KiB
Python
168 lines
5.2 KiB
Python
import enum
|
|
from dataclasses import dataclass
|
|
from typing import Iterable, Optional
|
|
|
|
from asn1crypto import cms
|
|
|
|
from pyhanko.sign.timestamps import TimeStamper
|
|
|
|
from ..attributes import CMSAttributeProvider, TSTProvider
|
|
from .cades_asn1 import (
|
|
CertifiedAttributeChoices,
|
|
CommitmentTypeIdentifier,
|
|
CommitmentTypeIndication,
|
|
SignaturePolicyIdentifier,
|
|
SignerAttributesV2,
|
|
)
|
|
|
|
__all__ = ['GenericCommitment', 'CAdESSignedAttrSpec', 'SignerAttrSpec']
|
|
|
|
|
|
# TODO add semantics explanations from the standard
|
|
|
|
|
|
@enum.unique
|
|
class GenericCommitment(enum.Enum):
|
|
PROOF_OF_ORIGIN = enum.auto()
|
|
PROOF_OF_RECEIPT = enum.auto()
|
|
PROOF_OF_DELIVERY = enum.auto()
|
|
PROOF_OF_SENDER = enum.auto()
|
|
PROOF_OF_APPROVAL = enum.auto()
|
|
PROOF_OF_CREATION = enum.auto()
|
|
|
|
@property
|
|
def asn1(self) -> CommitmentTypeIndication:
|
|
return CommitmentTypeIndication(
|
|
{'commitment_type_id': CommitmentTypeIdentifier(self.name.lower())}
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class SignerAttrSpec:
|
|
"""
|
|
Class that controls the ``signer-attributes-v2`` signed CAdES attribute.
|
|
|
|
These represent attributes of the signing entity, not the signature or
|
|
signed content.
|
|
|
|
.. note::
|
|
Out of the box, only basic claimed attributes and certified attributes
|
|
through V2 X.509 attribute certificates are supported.
|
|
"""
|
|
|
|
claimed_attrs: Iterable[cms.AttCertAttribute]
|
|
"""
|
|
Attributes claimed by the signer without further justification.
|
|
"""
|
|
|
|
certified_attrs: Iterable[cms.AttributeCertificateV2]
|
|
"""
|
|
Attribute certificates containing signer attributes.
|
|
"""
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CAdESSignedAttrSpec:
|
|
"""
|
|
Class that controls signed CAdES attributes on a PDF signature.
|
|
"""
|
|
|
|
commitment_type: Optional[CommitmentTypeIndication] = None
|
|
"""
|
|
Signature commitment type. Can be one of the standard values, or a custom
|
|
one.
|
|
"""
|
|
|
|
timestamp_content: bool = False
|
|
"""
|
|
Indicate whether the signature should include a signed timestamp.
|
|
|
|
.. note::
|
|
This should be contrasted with *unsigned* timestamps:
|
|
a signed timestamp proves that the signature was created *after* some
|
|
point in time, while an *unsigned* timestamp computed over the signed
|
|
content proves that the signature existed *before* said point in time.
|
|
"""
|
|
|
|
signature_policy_identifier: Optional[SignaturePolicyIdentifier] = None
|
|
"""
|
|
Signature policy identifier to embed into the signature.
|
|
|
|
.. warning::
|
|
Right now, pyHanko does not "understand" signature policies, so the
|
|
signature policy identifier will be taken at face value and embedded
|
|
without paying any heed to the actual rules of the signature policy.
|
|
It is the API user's responsibility to make sure that all relevant
|
|
provisions of the signature policy are adhered to.
|
|
"""
|
|
|
|
signer_attributes: Optional[SignerAttrSpec] = None
|
|
"""
|
|
Settings for signer's attributes, to be included in a
|
|
``signer-attributes-v2`` attribute on the signature.
|
|
"""
|
|
|
|
def prepare_providers(
|
|
self,
|
|
message_digest,
|
|
md_algorithm,
|
|
timestamper: Optional[TimeStamper] = None,
|
|
):
|
|
if self.timestamp_content and timestamper is not None:
|
|
yield TSTProvider(
|
|
digest_algorithm=md_algorithm,
|
|
data_to_ts=message_digest,
|
|
timestamper=timestamper,
|
|
attr_type='content_time_stamp',
|
|
prehashed=True,
|
|
)
|
|
if self.signature_policy_identifier is not None:
|
|
yield SigPolicyIDProvider(self.signature_policy_identifier)
|
|
if self.commitment_type is not None:
|
|
yield CommitmentTypeProvider(self.commitment_type)
|
|
if self.signer_attributes is not None:
|
|
yield SignerAttributesProvider(self.signer_attributes)
|
|
|
|
|
|
class CommitmentTypeProvider(CMSAttributeProvider):
|
|
attribute_type = 'commitment_type'
|
|
|
|
def __init__(self, commitment_type: CommitmentTypeIndication):
|
|
self.commitment_type = commitment_type
|
|
|
|
async def build_attr_value(self, dry_run=False) -> CommitmentTypeIndication:
|
|
return self.commitment_type
|
|
|
|
|
|
class SigPolicyIDProvider(CMSAttributeProvider):
|
|
attribute_type = 'signature_policy_identifier'
|
|
|
|
def __init__(self, policy_id: SignaturePolicyIdentifier):
|
|
self.policy_id = policy_id
|
|
|
|
async def build_attr_value(
|
|
self, dry_run=False
|
|
) -> SignaturePolicyIdentifier:
|
|
return self.policy_id
|
|
|
|
|
|
class SignerAttributesProvider(CMSAttributeProvider):
|
|
attribute_type = 'signer_attributes_v2'
|
|
|
|
def __init__(self, signer_attr_spec: SignerAttrSpec):
|
|
self.signer_attr_spec = signer_attr_spec
|
|
|
|
async def build_attr_value(self, dry_run=False) -> SignerAttributesV2:
|
|
spec = self.signer_attr_spec
|
|
claimed = list(spec.claimed_attrs)
|
|
certified = list(spec.certified_attrs)
|
|
result = {}
|
|
if claimed:
|
|
result['claimed_attributes'] = claimed
|
|
if certified:
|
|
result['certified_attributes_v2'] = [
|
|
CertifiedAttributeChoices(name='attr_cert', value=attr_cert)
|
|
for attr_cert in certified
|
|
]
|
|
return SignerAttributesV2(result)
|