diff --git a/pyhanko/sign/validation/ades.py b/pyhanko/sign/validation/ades.py index 51cc081c..75118a4f 100644 --- a/pyhanko/sign/validation/ades.py +++ b/pyhanko/sign/validation/ades.py @@ -44,6 +44,7 @@ CertValidationPolicySpec, ValidationDataHandlers, ) +from pyhanko_certvalidator.errors import PathError from pyhanko_certvalidator.ltv.ades_past import past_validate from pyhanko_certvalidator.ltv.poe import ( KnownPOE, @@ -58,6 +59,7 @@ from pyhanko_certvalidator.path import ValidationPath from pyhanko_certvalidator.policy_decl import ( AlgorithmUsagePolicy, + NonRevokedStatusAssertion, RevocationCheckingRule, ) from pyhanko_certvalidator.registry import PathBuilder, TrustManager @@ -1331,6 +1333,9 @@ async def ades_past_signature_validation( except errors.SignatureValidationError as e: logger.warning(e) return e.ades_subindication or AdESIndeterminate.GENERIC + except PathError as e: + logger.warning(e) + return AdESIndeterminate.CERTIFICATE_CHAIN_GENERAL_FAILURE @dataclass(frozen=True) @@ -1523,8 +1528,11 @@ def _build_prima_facie_poe_index_from_pdf_timestamps( if ts_signed_data is not None: # add DSS content - dss = DocumentSecurityStore.read_dss(hist_handler) - collected_so_far.update(_read_validation_objects_from_dss(dss)) + try: + dss = DocumentSecurityStore.read_dss(hist_handler) + collected_so_far.update(_read_validation_objects_from_dss(dss)) + except NoDSSFoundError: + pass collected_so_far.update(for_next_ts) doc_digest = embedded_sig.compute_digest() coverage_normal = ( @@ -1899,6 +1907,7 @@ async def ades_lta_validation( known_crls=init_local_knowledge.known_crls + dss_facts.known_crls, known_certs=init_local_knowledge.known_certs + dss_facts.known_certs, known_poes=init_local_knowledge.known_poes, + nonrevoked_assertions=init_local_knowledge.nonrevoked_assertions, ) augmented_validation_spec = dataclasses.replace( @@ -2116,12 +2125,6 @@ async def simulate_future_ades_lta_validation( :return: An AdES LTA validation result. """ - # TODO add a mode that validates the most recent docts live at the current - # reference time? Otherwise timestamp validation will typically fail - # if the gap between the timestamp and the time of the simulation is large, - # since the revinfo in the document for that timestamp will be stale - # (but if the TS cert is not expired, fresh revinfo should be available) - now = current_reference_time or datetime.now(tz=timezone.utc) timing_info = ValidationTimingInfo( validation_time=future_validation_time, @@ -2134,6 +2137,18 @@ async def simulate_future_ades_lta_validation( orig_sig_validation_spec = pdf_validation_spec.signature_validation_spec orig_local_knowledge = orig_sig_validation_spec.local_knowledge dss_knowledge = _dss_to_local_knowledge(embedded_sig.reader) + new_nonrevoked_assertions = list(orig_local_knowledge.nonrevoked_assertions) + # assert the nonrevoked status of the last timestamp cert, since we can't + # get "future" revinfo anyway + try: + last_ts = embedded_sig.reader.embedded_timestamp_signatures[-1] + new_nonrevoked_assertions.append( + NonRevokedStatusAssertion( + last_ts.signer_cert.sha256, at=future_validation_time + ) + ) + except IndexError: + pass def _poes(): # For the purposes of this test, we assert all proofs of existence @@ -2148,7 +2163,7 @@ def _poes(): # we don't validate the would-be POEs that are embedded in the # document at this point yield KnownPOE( - poe_type=POEType.VALIDATION, + poe_type=POEType.PROVIDED, digest=item.digest, poe_time=now, validation_object=item.validation_object, @@ -2157,6 +2172,7 @@ def _poes(): updated_local_knowledge = dataclasses.replace( orig_local_knowledge, known_poes=list(_poes()), + nonrevoked_assertions=new_nonrevoked_assertions, ) updated_pdf_validation_spec = dataclasses.replace( diff --git a/pyhanko/sign/validation/policy_decl.py b/pyhanko/sign/validation/policy_decl.py index ab00834a..0951da37 100644 --- a/pyhanko/sign/validation/policy_decl.py +++ b/pyhanko/sign/validation/policy_decl.py @@ -25,6 +25,7 @@ digest_for_poe, ) from pyhanko_certvalidator.ltv.types import ValidationTimingInfo +from pyhanko_certvalidator.policy_decl import NonRevokedStatusAssertion from pyhanko_certvalidator.revinfo.archival import CRLContainer, OCSPContainer from pyhanko.sign.diff_analysis import DEFAULT_DIFF_POLICY, DiffPolicy @@ -78,6 +79,9 @@ class LocalKnowledge: known_crls: List[CRLContainer] = field(default_factory=list) known_certs: List[x509.Certificate] = field(default_factory=list) known_poes: List[KnownPOE] = field(default_factory=list) + nonrevoked_assertions: List[NonRevokedStatusAssertion] = field( + default_factory=list + ) def add_to_poe_manager(self, poe_manager: POEManager): for poe in self.known_poes: @@ -180,5 +184,6 @@ def bootstrap_validation_data_handlers( ocsps=knowledge.known_ocsps, certs=knowledge.known_certs, poe_manager=poe_manager_override, + nonrevoked_assertions=knowledge.nonrevoked_assertions, ) return handlers diff --git a/pyhanko/sign/validation/report/tools.py b/pyhanko/sign/validation/report/tools.py index 1ca426cb..ba04fbd5 100644 --- a/pyhanko/sign/validation/report/tools.py +++ b/pyhanko/sign/validation/report/tools.py @@ -255,7 +255,7 @@ def _summarise_attrs( def _generate_report( embedded_sig: EmbeddedPdfSignature, status: AdESBasicValidationResult -): +) -> ts_11910202.SignatureValidationReportType: api_status: PdfSignatureStatus = cast(PdfSignatureStatus, status.api_status) # this is meaningless for EdDSA signatures, but the entry is mandatory, so... md_spec = get_pyca_cryptography_hash(api_status.md_algorithm) @@ -320,7 +320,7 @@ def _generate_report( signer_information=ts_11910202.SignerInformationType( signer_certificate=ts_11910202.VOReferenceType( voreference=( - f"#{derive_validation_object_identifier(signer_cert_vo)}", + f"{derive_validation_object_identifier(signer_cert_vo)}", ), ), ), @@ -333,7 +333,7 @@ def _generate_report( signature_validation_status=ts_11910202.ValidationStatusType( main_indication=ades_main_indic, sub_indication=( - f'urn:etsi:019102:subindication:{str(status.ades_subindic)}', + f'urn:etsi:019102:subindication:{status.ades_subindic.standard_name}', ), ), )