def parse_csc_auth_response(response_data: dict) -> CSCAuthorizationInfo: """ Parse the response from a ``credentials/authorize`` call into a :class:`.CSCAuthorizationInfo` object. :param response_data: The decoded response JSON. :return: A :class:`.CSCAuthorizationInfo` object. """ try: sad = str(response_data["SAD"]) except KeyError: raise SigningError( "Could not extract SAD value from auth response") try: lifetime_seconds = int(response_data.get('expiresIn', 3600)) now = datetime.now(tz=tzlocal.get_localzone()) expires_at = now + timedelta(seconds=lifetime_seconds) except ValueError as e: raise SigningError( "Could not process expiresIn value in auth response") from e return CSCAuthorizationInfo(sad=sad, expires_at=expires_at)
def _get_or_create_sigfield(field_name, pdf_out: BasePdfFileWriter, existing_fields_only, new_field_spec: Optional[SigFieldSpec] = None): root = pdf_out.root if field_name is None: if not existing_fields_only: raise SigningError('Not specifying a field name is only allowed ' 'when existing_fields_only=True') # most of the logic in prepare_sig_field has to do with preparing # for the potential addition of a new field. That is completely # irrelevant in this special case, so we might as well short circuit # things. field_created = False empty_fields = enumerate_sig_fields(pdf_out, filled_status=False) try: found_field_name, _, sig_field_ref = next(empty_fields) except StopIteration: raise SigningError('There are no empty signature fields.') others = ', '.join(fn for fn, _, _ in empty_fields if fn is not None) if others: raise SigningError( 'There are several empty signature fields. Please specify ' 'a field name. The options are %s, %s.' % (found_field_name, others)) else: # grab or create a sig field if new_field_spec is not None: sig_field_kwargs = { 'box': new_field_spec.box, 'include_on_page': pdf_out.find_page_for_modification(new_field_spec.on_page)[0], 'combine_annotation': new_field_spec.combine_annotation } else: sig_field_kwargs = {} field_created, sig_field_ref = prepare_sig_field( field_name, root, update_writer=pdf_out, existing_fields_only=existing_fields_only, **sig_field_kwargs) ensure_sig_flags(writer=pdf_out, lock_sig_flags=True) return field_created, sig_field_ref
def __init__(self, pkcs11_session: Session, cert_label: Optional[str] = None, signing_cert: x509.Certificate = None, ca_chain=None, key_label: Optional[str] = None, prefer_pss=False, embed_roots=True, other_certs_to_pull=(), bulk_fetch=True, key_id: Optional[bytes] = None, cert_id: Optional[bytes] = None, use_raw_mechanism=False): """ Initialise a PKCS11 signer. """ if signing_cert is None and cert_id is None and cert_label is None: raise SigningError( "Please specify a signer's certificate through the " "'cert_id', 'signing_cert' and/or 'cert_label' options") self.cert_label = cert_label self.key_id = key_id self.cert_id = cert_id self._signing_cert = signing_cert if key_id is None and key_label is None: if cert_label is None: raise SigningError( "If 'cert_label' is None, then 'key_label' or 'key_id' " "must be provided.") key_label = cert_label self.key_label = key_label self.pkcs11_session = pkcs11_session cs = SimpleCertificateStore() self._cert_registry: CertificateStore = cs if ca_chain is not None: cs.register_multiple(ca_chain) if signing_cert is not None: cs.register(signing_cert) self.other_certs = other_certs_to_pull self._other_certs_loaded = False if other_certs_to_pull is not None and len(other_certs_to_pull) <= 1: self.bulk_fetch = False else: self.bulk_fetch = bulk_fetch self.use_raw_mechanism = use_raw_mechanism self._key_handle = None self._loaded = False self.__loading_event = None super().__init__(prefer_pss=prefer_pss, embed_roots=embed_roots)
def _process_certificate_info_response(response_data) -> CSCCredentialInfo: try: b64_certs = response_data['cert']['certificates'] except KeyError as e: raise SigningError( "Could not retrieve certificates from response") from e try: certs = [ x509.Certificate.load(base64.b64decode(cert)) for cert in b64_certs ] except ValueError as e: raise SigningError("Could not decode certificates in response") from e try: algo_oids = response_data["key"]["algo"] if not isinstance(algo_oids, list): raise TypeError supported_algos = frozenset( algos.SignedDigestAlgorithmId(oid).native for oid in algo_oids) except (KeyError, ValueError, TypeError) as e: raise SigningError( "Could not retrieve supported signing mechanisms from response" ) from e try: max_batch_size = int(response_data['multisign']) except (KeyError, ValueError) as e: raise SigningError( "Could not retrieve max batch size from response") from e scal_value = response_data.get("SCAL", 1) try: scal_value = int(scal_value) if scal_value not in (1, 2): raise ValueError except ValueError: raise SigningError("SCAL value must be \"1\" or \"2\".") hash_pinning_required = scal_value == 2 return CSCCredentialInfo( # The CSC spec requires the signer's certificate to be first # in the 'certs' array. The order for the others is unspecified, # but that doesn't matter. signing_cert=certs[0], chain=certs[1:], supported_mechanisms=supported_algos, max_batch_size=max_batch_size, hash_pinning_required=hash_pinning_required, response_data=response_data)
async def async_sign_pdf(pdf_out: BasePdfFileWriter, signature_meta: PdfSignatureMetadata, signer: Signer, timestamper: TimeStamper = None, new_field_spec: Optional[SigFieldSpec] = None, existing_fields_only=False, bytes_reserved=None, in_place=False, output=None): """ Thin convenience wrapper around :meth:`.PdfSigner.async_sign_pdf`. :param pdf_out: An :class:`.IncrementalPdfFileWriter`. :param bytes_reserved: Bytes to reserve for the CMS object in the PDF file. If not specified, make an estimate based on a dummy signature. :param signature_meta: The specification of the signature to add. :param signer: :class:`.Signer` object to use to produce the signature object. :param timestamper: :class:`.TimeStamper` object to use to produce any time stamp tokens that might be required. :param in_place: Sign the input in-place. If ``False``, write output to a :class:`.BytesIO` object. :param existing_fields_only: If ``True``, never create a new empty signature field to contain the signature. If ``False``, a new field may be created if no field matching :attr:`~.PdfSignatureMetadata.field_name` exists. :param new_field_spec: If a new field is to be created, this parameter allows the caller to specify the field's properties in the form of a :class:`.SigFieldSpec`. This parameter is only meaningful if ``existing_fields_only`` is ``False``. :param output: Write the output to the specified output stream. If ``None``, write to a new :class:`.BytesIO` object. Default is ``None``. :return: The output stream containing the signed output. """ if new_field_spec is not None and existing_fields_only: raise SigningError( "Specifying a signature field spec is not meaningful when " "existing_fields_only=True.") signer = PdfSigner(signature_meta, signer, timestamper=timestamper, new_field_spec=new_field_spec) return await signer.async_sign_pdf( pdf_out, existing_fields_only=existing_fields_only, bytes_reserved=bytes_reserved, in_place=in_place, output=output)
def read_mdp_dict(mdp): try: val = mdp['/P'] return SeedSignatureType(None if val == 0 else MDPPerm(val)) except (KeyError, TypeError, ValueError): raise SigningError( f"/MDP entry {mdp} in seed value dictionary is not " "correctly formatted.")
def get_signature_mechanism(self, digest_algorithm): if self.signature_mechanism is not None: return self.signature_mechanism result = super().get_signature_mechanism(digest_algorithm) result_algo = result['algorithm'] supported = self.auth_manager.credential_info.supported_mechanisms if result_algo.native not in supported: raise SigningError( f"Signature mechanism {result_algo.native} is not supported, " f"must be one of {', '.join(alg for alg in supported)}.") return result
async def authorize_signature( self, hash_b64s: List[str]) -> CSCAuthorizationInfo: """ Return the prefetched SAD, or raise an error if called twice. :param hash_b64s: List of hashes to be signed; ignored. :return: The prefetched authorisation data. """ if self._used: raise SigningError("Prefetched SAD token is stale") self._used = True return self.csc_auth_info
async def _do_commit(self, batch: _CSCBatchInfo): """ Internal commit routine that skips error handling and concurrency checks. """ try: req_data = await self.format_csc_signing_req( batch.b64_hashes, batch.md_algorithm) session_info = self.auth_manager.csc_session_info url = session_info.endpoint_url("signatures/signHash") session = self.session async with session.post(url, headers=self.auth_manager.auth_headers, json=req_data, raise_for_status=True, timeout=self.sign_timeout) as response: response_data = await response.json() sig_b64s = response_data['signatures'] actual_len = len(sig_b64s) expected_len = len(batch.b64_hashes) if actual_len != expected_len: raise SigningError( f"Expected {expected_len} signatures, got {actual_len}") signatures = [base64.b64decode(sig) for sig in sig_b64s] batch.results = signatures except SigningError: raise except (ValueError, KeyError, TypeError) as e: raise SigningError( "Expected response with b64-encoded signature values") from e except aiohttp.ClientError as e: raise SigningError("Signature request failed") from e finally: self._current_batch = None batch.notifier.set()
def fill_reserved_region(self, output: IO, content_bytes: bytes): """ Write hex-encoded contents to the reserved region indicated by :attr:`reserved_region_start` and :attr:`reserved_region_end` in the output stream. :param output: Output stream to use. Must be writable and seekable. :param content_bytes: Content bytes. These will be padded, hexadecimally encoded and written to the appropriate location in output stream. :return: A :class:`bytes` object containing the contents that were written, plus any additional padding. """ content_hex = binascii.hexlify(content_bytes).upper() start = self.reserved_region_start end = self.reserved_region_end # might as well compute this bytes_reserved = end - start - 2 length = len(content_hex) if length > bytes_reserved: raise SigningError( f"Final ByteRange payload larger than expected: " f"allocated {bytes_reserved} bytes, but contents " f"required {length} bytes.") # pragma: nocover # +1 to skip the '<' output.seek(start + 1) # NOTE: the PDF spec is not completely clear on this, but # signature contents are NOT supposed to be encrypted. # Perhaps this falls under the "strings in encrypted containers" # denominator in § 7.6.1? # Addition: the PDF 2.0 spec *does* spell out that this content # is not to be encrypted. output.write(content_hex) output.seek(0) padding = bytes(bytes_reserved // 2 - len(content_bytes)) return content_bytes + padding
async def authorize_signature( self, hash_b64s: List[str]) -> CSCAuthorizationInfo: self.authorizations_requested += 1 session_info = self.csc_session_info req_data = self.format_csc_auth_request(hash_b64s=hash_b64s) session = self.session url = session_info.endpoint_url("credentials/authorize") async with session.post(url, headers=self.auth_headers, json=req_data, raise_for_status=True, timeout=30) as response: try: response_data = await response.json() except aiohttp.ClientError as e: raise SigningError("Credential auth request failed") from e if self.waste_time: await asyncio.sleep(self.waste_time) return self.parse_csc_auth_response(response_data)
async def commit(self): """ Commit the current batch by calling the ``signatures/signHash`` endpoint on the CSC service. This coroutine does not return anything; instead, it notifies all waiting signing coroutines that their signature has been fetched. """ batch = self._current_batch if batch is None or batch.results is not None: return elif batch.initiated: # just wait for the commit to finish together with # all the signers in the queue await batch.notifier.wait() if not batch.results: raise SigningError("Commit failed") else: batch.initiated = True await self._do_commit(batch)
async def _ensure_batch(self, digest_algorithm) -> _CSCBatchInfo: while self._current_batch is not None and self._current_batch.initiated: logger.debug("Commit ongoing... Waiting for it to finish") # There's a commit going on, wait for it to finish await self._current_batch.notifier.wait() logger.debug(f"Done waiting for commit: " f"new batch: {repr(self._current_batch)})") # ...and start a new batch right after (unless someone else # already did, or the new batch is somehow already full/already # committing, in which case we have to keep queueing) if self._current_batch is not None: batch = self._current_batch if batch.md_algorithm != digest_algorithm: raise SigningError( f"All signatures in the same batch must use the same " f"digest function; encountered both {batch.md_algorithm} " f"and {digest_algorithm}.") return batch self._current_batch = batch = _CSCBatchInfo( notifier=asyncio.Event(), md_algorithm=digest_algorithm, ) return batch
async def async_sign_raw(self, data: bytes, digest_algorithm: str, dry_run=False) -> bytes: if dry_run: return bytes(self.est_raw_signature_size) tbs_hash = base64_digest(data, digest_algorithm) # ensure that there's a batch that we can hitch a ride on batch = await self._ensure_batch(digest_algorithm) ix = batch.add(tbs_hash) # autocommit if the batch is full if self.batch_autocommit and ix == self.batch_size - 1: try: await self.commit() except SigningError as e: # log and move on, we'll throw a regular exception later logger.error("Failed to commit signatures", exc_info=e) # Sleep until a commit goes through await batch.notifier.wait() if not batch.results: raise SigningError("No signing results available") return batch.results[ix]
async def fetch_certs_in_csc_credential( session: aiohttp.ClientSession, csc_session_info: CSCServiceSessionInfo, timeout: int = 30) -> CSCCredentialInfo: """ Call the ``credentials/info`` endpoint of the CSC service for a specific credential, and encode the result into a :class:`.CSCCredentialInfo` object. :param session: The ``aiohttp`` session to use when performing queries. :param csc_session_info: General information about the CSC service and the credential. :param timeout: How many seconds to allow before time-out. :return: A :class:`.CSCCredentialInfo` object with the processed response. """ url = csc_session_info.endpoint_url("credentials/info") req_data = { "credentialID": csc_session_info.credential_id, "certificates": "chain", "certInfo": False } try: async with session.post(url, headers=csc_session_info.auth_headers, json=req_data, raise_for_status=True, timeout=timeout) as response: response_data = await response.json() except aiohttp.ClientError as e: raise SigningError("Credential info request failed") from e return _process_certificate_info_response(response_data)
def from_pdf_object(cls, pdf_dict): """ Read from a seed value dictionary. :param pdf_dict: A :class:`~.generic.DictionaryObject`. :return: A :class:`.SigSeedValueSpec` object. """ if isinstance(pdf_dict, generic.IndirectObject): pdf_dict = pdf_dict.get_object() try: if pdf_dict['/Type'] != '/SV': # pragma: nocover raise ValueError('Object /Type entry is not /SV') except KeyError: # pragma: nocover pass flags = SigSeedValFlags(pdf_dict.get('/Ff', 0)) try: sig_filter = pdf_dict['/Filter'] if (flags & SigSeedValFlags.FILTER) and \ (sig_filter != '/Adobe.PPKLite'): raise SigningError( "Signature handler '%s' is not available, only the " "default /Adobe.PPKLite is supported." % sig_filter) except KeyError: pass # TODO support all PDF 2.0 values min_version = pdf_dict.get('/V', 1) if flags & SigSeedValFlags.V and min_version > 1: raise SigningError( "Seed value dictionary version %s not supported." % min_version) try: add_rev_info = bool(pdf_dict['/AddRevInfo']) except KeyError: add_rev_info = None subfilter_reqs = pdf_dict.get('/SubFilter', None) subfilters = None if subfilter_reqs is not None: def _subfilters(): for s in subfilter_reqs: try: yield SigSeedSubFilter(s) except ValueError: pass subfilters = list(_subfilters()) try: digest_methods = [s.lower() for s in pdf_dict['/DigestMethod']] except KeyError: digest_methods = None reasons = get_and_apply(pdf_dict, '/Reasons', list) timestamp_dict = pdf_dict.get('/TimeStamp', {}) timestamp_server_url = timestamp_dict.get('/URL', None) timestamp_required = bool(timestamp_dict.get('/Ff', 0)) cert_constraints = pdf_dict.get('/Cert', None) if cert_constraints is not None: cert_constraints = SigCertConstraints.from_pdf_object( cert_constraints) return cls(flags=flags, reasons=reasons, timestamp_server_url=timestamp_server_url, cert=cert_constraints, subfilters=subfilters, digest_methods=digest_methods, add_rev_info=add_rev_info, timestamp_required=timestamp_required)
def write_cms(self, field_name: str, writer: BasePdfFileWriter, existing_fields_only=False): """ .. versionadded:: 0.3.0 .. versionchanged:: 0.7.0 Digest wrapped in :class:`~pyhanko.sign.signers.pdf_byterange.PreparedByteRangeDigest` in step 3; ``output`` returned in step 3 instead of step 4. This method returns a generator coroutine that controls the process of embedding CMS data into a PDF signature field. Can be used for both timestamps and regular signatures. .. danger:: This is a very low-level interface that performs virtually no error checking, and is intended to be used in situations where the construction of the CMS object to be embedded is not under the caller's control (e.g. a remote signer that produces full-fledged CMS objects). In almost every other case, you're better of using :class:`.PdfSigner` instead, with a custom :class:`.Signer` implementation to handle the cryptographic operations if necessary. The coroutine follows the following specific protocol. 1. First, it retrieves or creates the signature field to embed the CMS object in, and yields a reference to said field. 2. The caller should then send in a :class:`.SigObjSetup` object, which is subsequently processed by the coroutine. For convenience, the coroutine will then yield a reference to the signature dictionary (as embedded in the PDF writer). 3. Next, the caller should send a :class:`.SigIOSetup` object, describing how the resulting document should be hashed and written to the output. The coroutine will write the entire document with a placeholder region reserved for the signature and compute the document's hash and yield it to the caller. It will then yield a ``prepared_digest, output`` tuple, where ``prepared_digest`` is a :class:`.PreparedByteRangeDigest` object containing the document digest and the relevant offsets, and ``output`` is the output stream to which the document to be signed was written. From this point onwards, **no objects may be changed or added** to the :class:`.IncrementalPdfFileWriter` currently in use. 4. Finally, the caller should pass in a CMS object to place inside the signature dictionary. The CMS object can be supplied as a raw :class:`bytes` object, or an :mod:`asn1crypto`-style object. The coroutine's final yield is the value of the signature dictionary's ``/Contents`` entry, given as a hexadecimal string. .. caution:: It is the caller's own responsibility to ensure that enough room is available in the placeholder signature object to contain the final CMS object. :param field_name: The name of the field to fill in. This should be a field of type ``/Sig``. :param writer: An :class:`.IncrementalPdfFileWriter` containing the document to sign. :param existing_fields_only: If ``True``, never create a new empty signature field to contain the signature. If ``False``, a new field may be created if no field matching ``field_name`` exists. :return: A generator coroutine implementing the protocol described above. """ new_field_spec = self.new_field_spec \ if not existing_fields_only else None # start by creating or fetching the appropriate signature field field_created, sig_field_ref = _get_or_create_sigfield( field_name, writer, existing_fields_only, new_field_spec=new_field_spec) # yield control to caller to further process the field dictionary # if necessary, request setup specs for sig object sig_obj_setup = yield sig_field_ref assert isinstance(sig_obj_setup, SigObjSetup) sig_field = sig_field_ref.get_object() # take care of the field's visual appearance (if applicable) appearance_setup = sig_obj_setup.appearance_setup if appearance_setup is not None: try: sig_annot, = sig_field['/Kids'] sig_annot = sig_annot.get_object() except (ValueError, TypeError): raise SigningError( "Failed to access signature field's annotation. " "Signature field must have exactly one child annotation, " "or it must be combined with its annotation.") except KeyError: sig_annot = sig_field appearance_setup.apply(sig_annot, writer) sig_obj = sig_obj_setup.sig_placeholder sig_obj_ref = writer.add_object(sig_obj) # fill in a reference to the (empty) signature object sig_field[pdf_name('/V')] = sig_obj_ref if not field_created: # still need to mark it for updating writer.mark_update(sig_field_ref) mdp_setup = sig_obj_setup.mdp_setup if mdp_setup is not None: mdp_setup.apply(sig_obj_ref, writer) # again, pass control to the caller # and request I/O parameters for putting the cryptographic signature # into the output. # We pass a reference to the embedded signature object as a convenience. sig_io = yield sig_obj_ref assert isinstance(sig_io, SigIOSetup) # pass control to the sig object's write_signature coroutine yield from sig_obj.fill(writer, sig_io.md_algorithm, in_place=sig_io.in_place, output=sig_io.output, chunk_size=sig_io.chunk_size)
def _prepare_sig_field(sig_field_name, root, update_writer: BasePdfFileWriter, existing_fields_only=False, lock_sig_flags=True, **kwargs): """ Returns a tuple of a boolean and a reference to a signature field. The boolean is ``True`` if the field was created, and ``False`` otherwise. """ if sig_field_name is None: # pragma: nocover raise ValueError try: form = root['/AcroForm'] try: fields = form['/Fields'] except KeyError: raise ValueError('/AcroForm has no /Fields') candidates = enumerate_sig_fields_in(fields, with_name=sig_field_name) sig_field_ref = None try: field_name, value, sig_field_ref = next(candidates) if value is not None: raise SigningError( 'Signature field with name %s appears to be filled already.' % sig_field_name) except StopIteration: if existing_fields_only: raise SigningError( 'No empty signature field with name %s found.' % sig_field_name) form_created = False except KeyError: # we have to create the form if existing_fields_only: raise SigningError('This file does not contain a form.') # no AcroForm present, so create one form = generic.DictionaryObject() root[pdf_name('/AcroForm')] = update_writer.add_object(form) fields = generic.ArrayObject() form[pdf_name('/Fields')] = fields # now we need to mark the root as updated update_writer.update_root() form_created = True sig_field_ref = None if sig_field_ref is not None: return False, sig_field_ref # no signature field exists, so create one # default: grab a reference to the first page page_ref = update_writer.find_page_for_modification(0)[0] sig_form_kwargs = {'include_on_page': page_ref} sig_form_kwargs.update(**kwargs) sig_field = SignatureFormField(sig_field_name, **sig_form_kwargs) created, sig_field_ref = _insert_or_get_field_at( update_writer, fields, path=sig_field_name.split('.'), field_obj=sig_field) update_writer.register_annotation(page_ref, sig_field_ref) # make sure /SigFlags is present. If not, create it sig_flags = 3 if lock_sig_flags else 1 form.setdefault(pdf_name('/SigFlags'), generic.NumberObject(sig_flags)) # if a field was added to an existing form, register an extra update if not form_created: update_writer.update_container(fields) return True, sig_field_ref
def enumerate_sig_fields_in(field_list, filled_status=None, with_name=None, parent_name="", parents=None): if not isinstance(field_list, generic.ArrayObject): logger.warning( f"Values of type {type(field_list)} are not valid as field " f"lists, must be array objects -- skipping.") return parents = parents or () for field_ref in field_list: if not isinstance(field_ref, generic.IndirectObject): logger.warning( "Entries in field list must be indirect references -- skipping." ) continue field = field_ref.get_object() if not isinstance(field, generic.DictionaryObject): logger.warning( "Entries in field list must be dictionary objects, not " f"{type(field)} -- skipping.") continue # /T is the field name. If not specified, we're dealing with a bare # widget, so skip it. (these should never occur in /Fields, but hey) try: field_name = field['/T'] except KeyError: continue fq_name = field_name if not parent_name else ( "%s.%s" % (parent_name, field_name)) explicitly_requested = with_name is not None and fq_name == with_name child_requested = explicitly_requested or ( with_name is not None and with_name.startswith(fq_name)) # /FT is inheritable, so go up the chain current_path = (field, ) + parents for parent_field in current_path: try: field_type = parent_field['/FT'] break except KeyError: continue else: field_type = None if field_type == '/Sig': field_value = field.get('/V') # "cast" to a regular string object filled = field_value is not None status_check = filled_status is None or filled == filled_status name_check = with_name is None or explicitly_requested if status_check and name_check: yield fq_name, field_value, field_ref elif explicitly_requested: raise SigningError( 'Field with name %s exists but is not a signature field' % fq_name) # if necessary, descend into the field hierarchy if with_name is None or (child_requested and not explicitly_requested): try: yield from enumerate_sig_fields_in(field['/Kids'], parent_name=fq_name, parents=current_path, with_name=with_name, filled_status=filled_status) except KeyError: continue
def from_pdf_object(cls, pdf_dict): """ Read from a seed value dictionary. :param pdf_dict: A :class:`~.generic.DictionaryObject`. :return: A :class:`.SigSeedValueSpec` object. """ if isinstance(pdf_dict, generic.IndirectObject): pdf_dict = pdf_dict.get_object() try: if pdf_dict['/Type'] != '/SV': # pragma: nocover raise ValueError('Object /Type entry is not /SV') except KeyError: # pragma: nocover pass flags = SigSeedValFlags(pdf_dict.get('/Ff', 0)) try: sig_filter = pdf_dict['/Filter'] if (flags & SigSeedValFlags.FILTER) and \ (sig_filter != '/Adobe.PPKLite'): raise SigningError( "Signature handler '%s' is not available, only the " "default /Adobe.PPKLite is supported." % sig_filter) except KeyError: pass try: min_version = pdf_dict['/V'] supported = SeedValueDictVersion.PDF_2_0.value if flags & SigSeedValFlags.V and min_version > supported: raise SigningError( "Seed value dictionary version %s not supported." % min_version) min_version = SeedValueDictVersion(min_version) except KeyError: min_version = None try: add_rev_info = bool(pdf_dict['/AddRevInfo']) except KeyError: add_rev_info = None subfilter_reqs = pdf_dict.get('/SubFilter', None) subfilters = None if subfilter_reqs is not None: def _subfilters(): for s in subfilter_reqs: try: yield SigSeedSubFilter(s) except ValueError: pass subfilters = list(_subfilters()) try: digest_methods = [s.lower() for s in pdf_dict['/DigestMethod']] except KeyError: digest_methods = None reasons = get_and_apply(pdf_dict, '/Reasons', list) legal_attestations = get_and_apply(pdf_dict, '/LegalAttestation', list) def read_mdp_dict(mdp): try: val = mdp['/P'] return SeedSignatureType(None if val == 0 else MDPPerm(val)) except (KeyError, TypeError, ValueError): raise SigningError( f"/MDP entry {mdp} in seed value dictionary is not " "correctly formatted.") signature_type = get_and_apply(pdf_dict, '/MDP', read_mdp_dict) def read_lock_document(val): try: return SeedLockDocument(val) except ValueError: raise SigningError(f"/LockDocument entry '{val}' is invalid.") lock_document = get_and_apply(pdf_dict, '/LockDocument', read_lock_document) appearance_filter = pdf_dict.get('/AppearanceFilter', None) timestamp_dict = pdf_dict.get('/TimeStamp', {}) timestamp_server_url = timestamp_dict.get('/URL', None) timestamp_required = bool(timestamp_dict.get('/Ff', 0)) cert_constraints = pdf_dict.get('/Cert', None) if cert_constraints is not None: cert_constraints = SigCertConstraints.from_pdf_object( cert_constraints) return cls(flags=flags, reasons=reasons, timestamp_server_url=timestamp_server_url, cert=cert_constraints, subfilters=subfilters, digest_methods=digest_methods, add_rev_info=add_rev_info, timestamp_required=timestamp_required, legal_attestations=legal_attestations, seed_signature_type=signature_type, sv_dict_version=min_version, lock_document=lock_document, appearance=appearance_filter)
def read_lock_document(val): try: return SeedLockDocument(val) except ValueError: raise SigningError(f"/LockDocument entry '{val}' is invalid.")