233 lines
5.9 KiB
Python
233 lines
5.9 KiB
Python
# coding: utf-8
|
|
from datetime import datetime
|
|
from typing import List, Optional, Type, TypeVar
|
|
|
|
from asn1crypto.crl import CRLReason
|
|
from cryptography.exceptions import InvalidSignature
|
|
|
|
from pyhanko_certvalidator._state import ValProcState
|
|
from pyhanko_certvalidator.path import ValidationPath
|
|
|
|
|
|
class PathError(Exception):
|
|
pass
|
|
|
|
|
|
class PathBuildingError(PathError):
|
|
pass
|
|
|
|
|
|
class CertificateFetchError(PathBuildingError):
|
|
pass
|
|
|
|
|
|
class CRLValidationError(Exception):
|
|
pass
|
|
|
|
|
|
class CRLNoMatchesError(CRLValidationError):
|
|
pass
|
|
|
|
|
|
class CRLFetchError(CRLValidationError):
|
|
pass
|
|
|
|
|
|
class CRLValidationIndeterminateError(CRLValidationError):
|
|
def __init__(
|
|
self,
|
|
msg: str,
|
|
failures: List[str],
|
|
suspect_stale: Optional[datetime] = None,
|
|
):
|
|
self.msg = msg
|
|
self.failures = failures
|
|
self.suspect_stale = suspect_stale
|
|
super().__init__(msg, failures)
|
|
|
|
|
|
class OCSPValidationError(Exception):
|
|
pass
|
|
|
|
|
|
class OCSPNoMatchesError(OCSPValidationError):
|
|
pass
|
|
|
|
|
|
class OCSPValidationIndeterminateError(OCSPValidationError):
|
|
def __init__(
|
|
self,
|
|
msg: str,
|
|
failures: List[str],
|
|
suspect_stale: Optional[datetime] = None,
|
|
):
|
|
self.msg = msg
|
|
self.failures = failures
|
|
self.suspect_stale = suspect_stale
|
|
super().__init__(msg, failures)
|
|
|
|
|
|
class OCSPFetchError(OCSPValidationError):
|
|
pass
|
|
|
|
|
|
class ValidationError(Exception):
|
|
def __init__(self, message: str):
|
|
self.failure_msg = message
|
|
super().__init__(message)
|
|
|
|
|
|
TPathErr = TypeVar('TPathErr', bound='PathValidationError')
|
|
|
|
|
|
class PathValidationError(ValidationError):
|
|
@classmethod
|
|
def from_state(
|
|
cls: Type[TPathErr], msg: str, proc_state: ValProcState
|
|
) -> TPathErr:
|
|
return cls(msg, proc_state=proc_state)
|
|
|
|
def __init__(self, msg: str, *, proc_state: ValProcState):
|
|
self.is_ee_cert = proc_state.is_ee_cert
|
|
self.is_side_validation = proc_state.is_side_validation
|
|
current = proc_state.cert_path_stack.head
|
|
orig = proc_state.cert_path_stack.last
|
|
assert current is not None and orig is not None
|
|
self.current_path: ValidationPath = current
|
|
self.original_path: ValidationPath = orig
|
|
super().__init__(msg)
|
|
|
|
|
|
class RevokedError(PathValidationError):
|
|
@classmethod
|
|
def format(
|
|
cls,
|
|
reason: CRLReason,
|
|
revocation_dt: datetime,
|
|
revinfo_type: str,
|
|
proc_state: ValProcState,
|
|
):
|
|
reason_str = reason.human_friendly
|
|
date = revocation_dt.strftime('%Y-%m-%d')
|
|
time = revocation_dt.strftime('%H:%M:%S')
|
|
msg = (
|
|
f'{revinfo_type} indicates {proc_state.describe_cert()} '
|
|
f'was revoked at {time} on {date}, due to {reason_str}.'
|
|
)
|
|
return RevokedError(msg, reason, revocation_dt, proc_state)
|
|
|
|
def __init__(
|
|
self,
|
|
msg,
|
|
reason: CRLReason,
|
|
revocation_dt: datetime,
|
|
proc_state: ValProcState,
|
|
):
|
|
self.reason = reason
|
|
self.revocation_dt = revocation_dt
|
|
super().__init__(msg, proc_state=proc_state)
|
|
|
|
|
|
class InsufficientRevinfoError(PathValidationError):
|
|
pass
|
|
|
|
|
|
class StaleRevinfoError(InsufficientRevinfoError):
|
|
@classmethod
|
|
def format(
|
|
cls,
|
|
msg: str,
|
|
time_cutoff: datetime,
|
|
proc_state: ValProcState,
|
|
):
|
|
return StaleRevinfoError(msg, time_cutoff, proc_state)
|
|
|
|
def __init__(
|
|
self, msg: str, time_cutoff: datetime, proc_state: ValProcState
|
|
):
|
|
self.time_cutoff = time_cutoff
|
|
super().__init__(msg, proc_state=proc_state)
|
|
|
|
|
|
class InsufficientPOEError(PathValidationError):
|
|
pass
|
|
|
|
|
|
class ExpiredError(PathValidationError):
|
|
@classmethod
|
|
def format(
|
|
cls,
|
|
*,
|
|
expired_dt: datetime,
|
|
proc_state: ValProcState,
|
|
):
|
|
msg = (
|
|
f"The path could not be validated because "
|
|
f"{proc_state.describe_cert()} expired "
|
|
f"{expired_dt.strftime('%Y-%m-%d %H:%M:%SZ')}"
|
|
)
|
|
return ExpiredError(msg, expired_dt, proc_state)
|
|
|
|
def __init__(self, msg, expired_dt: datetime, proc_state: ValProcState):
|
|
self.expired_dt = expired_dt
|
|
super().__init__(msg, proc_state=proc_state)
|
|
|
|
|
|
class NotYetValidError(PathValidationError):
|
|
@classmethod
|
|
def format(
|
|
cls,
|
|
*,
|
|
valid_from: datetime,
|
|
proc_state: ValProcState,
|
|
):
|
|
msg = (
|
|
f"The path could not be validated because "
|
|
f"{proc_state.describe_cert()} is not valid until "
|
|
f"{valid_from.strftime('%Y-%m-%d %H:%M:%SZ')}"
|
|
)
|
|
return NotYetValidError(msg, valid_from, proc_state)
|
|
|
|
def __init__(self, msg, valid_from: datetime, proc_state: ValProcState):
|
|
self.valid_from = valid_from
|
|
super().__init__(msg, proc_state=proc_state)
|
|
|
|
|
|
class InvalidCertificateError(ValidationError):
|
|
pass
|
|
|
|
|
|
class DisallowedAlgorithmError(PathValidationError):
|
|
def __init__(
|
|
self, *args, banned_since: Optional[datetime] = None, **kwargs
|
|
):
|
|
self.banned_since = banned_since
|
|
super().__init__(*args, **kwargs)
|
|
|
|
@classmethod
|
|
def from_state(
|
|
cls,
|
|
msg: str,
|
|
proc_state: ValProcState,
|
|
banned_since: Optional[datetime] = None,
|
|
) -> 'DisallowedAlgorithmError':
|
|
return cls(msg, banned_since=banned_since, proc_state=proc_state)
|
|
|
|
|
|
class InvalidAttrCertificateError(InvalidCertificateError):
|
|
pass
|
|
|
|
|
|
class PSSParameterMismatch(InvalidSignature):
|
|
pass
|
|
|
|
|
|
class DSAParametersUnavailable(InvalidSignature):
|
|
# TODO Technically, such a signature isn't _really_ invalid
|
|
# (we merely couldn't validate it).
|
|
# However, this is only an issue for CRLs and OCSP responses that
|
|
# make use of DSA parameter inheritance, which is pretty much a
|
|
# completely irrelevant problem in this day and age, so treating those
|
|
# signatures as invalid as a matter of course seems pretty much OK.
|
|
pass
|