1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
import dataclasses
import logging
from datetime import datetime, timezone
from typing import Optional
from pyhanko_certvalidator.context import (
CertValidationPolicySpec,
ValidationDataHandlers,
)
from pyhanko_certvalidator.errors import ValidationError
from pyhanko_certvalidator.ltv.errors import (
PastValidatePrecheckFailure,
TimeSlideFailure,
)
from pyhanko_certvalidator.ltv.time_slide import time_slide
from pyhanko_certvalidator.ltv.types import ValidationTimingInfo
from pyhanko_certvalidator.path import ValidationPath
from pyhanko_certvalidator.policy_decl import (
NO_REVOCATION,
AcceptAllAlgorithms,
CertRevTrustPolicy,
)
from pyhanko_certvalidator.validate import async_validate_path
__all__ = ['past_validate']
logger = logging.getLogger(__name__)
async def _past_validate_precheck(
path: ValidationPath,
validation_policy_spec: CertValidationPolicySpec,
):
# The past validation algorithm requires us to run the "regular"
# validation algorithm without regard for revocation and expiration
# on a known-good time
# Shell model: intersect the validity windows of all certs in the path
certs = list(path.iter_certs(include_root=False))
lower_bound = max(c.not_valid_before for c in certs)
upper_bound = min(c.not_valid_after for c in certs)
if lower_bound >= upper_bound:
raise PastValidatePrecheckFailure(
"The intersection of the validity periods of the certificates "
"in the path is empty or degenerate."
)
ref_time = ValidationTimingInfo(
validation_time=upper_bound,
best_signature_time=upper_bound,
point_in_time_validation=True,
)
validation_context = dataclasses.replace(
validation_policy_spec,
revinfo_policy=CertRevTrustPolicy(
revocation_checking_policy=NO_REVOCATION
),
algorithm_usage_policy=AcceptAllAlgorithms(),
).build_validation_context(timing_info=ref_time, handlers=None)
try:
await async_validate_path(
validation_context,
path,
validation_policy_spec.pkix_validation_params,
)
except ValidationError as e:
raise PastValidatePrecheckFailure(
"Elementary path validation routine failed during pre-check "
"for past point-in-time validation"
) from e
async def past_validate(
path: ValidationPath,
validation_policy_spec: CertValidationPolicySpec,
validation_data_handlers: ValidationDataHandlers,
init_control_time: Optional[datetime] = None,
best_signature_time: Optional[datetime] = None,
) -> datetime:
"""
Execute the ETSI EN 319 102-1 past certificate validation algorithm
against the given path (ETSI EN 319 102-1, ยง 5.6.2.1).
Instead of merely evaluating X.509 validation constraints, the algorithm
will perform a full point-in-time reevaluation of the path at the
control time mandated by the specification. This implies that a caller
implementing the past signature validation algorithm no longer needs to
explicitly reevaluate CA certificate revocation times and/or algorithm
constraints based on POEs.
.. warning::
This is incubating internal API.
:param path:
The prospective validation path against which to execute the algorithm.
:param validation_policy_spec:
The validation policy specification.
:param validation_data_handlers:
The handlers used to manage collected certificates,revocation
information and proof-of-existence records.
:param init_control_time:
Initial control time; defaults to the current time.
:param best_signature_time:
Usage time to use in freshness computations.
:return:
The control time returned by the time sliding algorithm.
Informally, the last time at which the certificate was known to be
valid.
"""
await _past_validate_precheck(
path,
validation_policy_spec,
)
try:
# time slide
init_control_time = init_control_time or datetime.now(tz=timezone.utc)
control_time = await time_slide(
path,
init_control_time=init_control_time,
rev_trust_policy=validation_policy_spec.revinfo_policy,
algo_usage_policy=validation_policy_spec.algorithm_usage_policy,
time_tolerance=validation_policy_spec.time_tolerance,
revinfo_manager=validation_data_handlers.revinfo_manager,
)
logger.info(
f"AdES time slide yields %s as the control time for path with "
f"leaf {path.describe_leaf()}",
control_time,
)
except ValidationError as e:
raise TimeSlideFailure(
f"Failed to get control time for point-in-time validation for path "
f"with leaf {path.describe_leaf()}"
) from e
ref_time = ValidationTimingInfo(
validation_time=control_time,
best_signature_time=best_signature_time or control_time,
point_in_time_validation=True,
)
# -> validate
validation_context = validation_policy_spec.build_validation_context(
timing_info=ref_time, handlers=validation_data_handlers
)
# Maintenance note:
# Doing a full point-in-time re-validation of the path is much more
# heavy-handed than what the AdES spec requires. We really only have to
# evaluate the chain constraints here.
# However, the past signature validation algorithm needs information about
# revocations up the chain and algorithm usage for _all_ operations in
# the validation process which is hard to pass on given the current
# architecture of certvalidator. Reevaluating with a time in the past
# is easier, and the POE enforcement is the same either way.
await async_validate_path(
validation_context,
path,
parameters=validation_policy_spec.pkix_validation_params,
)
return control_time
|