File: ades_past.py

package info (click to toggle)
python-pyhanko-certvalidator 0.26.3-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,956 kB
  • sloc: python: 9,254; sh: 47; makefile: 4
file content (168 lines) | stat: -rw-r--r-- 6,053 bytes parent folder | download | duplicates (2)
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