330 lines
11 KiB
Python
330 lines
11 KiB
Python
import asyncio
|
|
import warnings
|
|
from typing import Iterable, Optional
|
|
|
|
from asn1crypto import x509
|
|
|
|
from ._types import type_name
|
|
from .context import ValidationContext
|
|
from .errors import InvalidCertificateError, PathBuildingError, ValidationError
|
|
from .path import ValidationPath
|
|
from .policy_decl import PKIXValidationParams
|
|
from .util import CancelableAsyncIterator
|
|
from .validate import async_validate_path, validate_tls_hostname, validate_usage
|
|
from .version import __version__, __version_info__
|
|
|
|
__all__ = [
|
|
'__version__',
|
|
'__version_info__',
|
|
'CertificateValidator',
|
|
'ValidationContext',
|
|
'PKIXValidationParams',
|
|
'find_valid_path',
|
|
]
|
|
|
|
|
|
async def find_valid_path(
|
|
certificate: x509.Certificate,
|
|
paths: CancelableAsyncIterator[ValidationPath],
|
|
validation_context: ValidationContext,
|
|
pkix_validation_params: Optional[PKIXValidationParams] = None,
|
|
):
|
|
exceptions = []
|
|
try:
|
|
async for candidate_path in paths:
|
|
try:
|
|
await async_validate_path(
|
|
validation_context, candidate_path, pkix_validation_params
|
|
)
|
|
return candidate_path
|
|
except ValidationError as e:
|
|
exceptions.append(e)
|
|
except PathBuildingError:
|
|
if certificate.self_signed in {'yes', 'maybe'}:
|
|
raise InvalidCertificateError(
|
|
f'The X.509 certificate provided is self-signed - '
|
|
f'"{certificate.subject.human_friendly}"'
|
|
)
|
|
raise
|
|
finally:
|
|
await paths.cancel()
|
|
|
|
if len(exceptions) == 1:
|
|
raise exceptions[0]
|
|
|
|
non_signature_exception = None
|
|
for exception in exceptions:
|
|
if 'signature' not in str(exception):
|
|
non_signature_exception = exception
|
|
|
|
if non_signature_exception:
|
|
raise non_signature_exception
|
|
|
|
raise exceptions[0]
|
|
|
|
|
|
class CertificateValidator:
|
|
# A pyhanko_certvalidator.path.ValidationPath object - only set once validated
|
|
_path = None
|
|
|
|
def __init__(
|
|
self,
|
|
end_entity_cert: x509.Certificate,
|
|
intermediate_certs: Optional[Iterable[x509.Certificate]] = None,
|
|
validation_context: Optional[ValidationContext] = None,
|
|
pkix_params: Optional[PKIXValidationParams] = None,
|
|
):
|
|
"""
|
|
:param end_entity_cert:
|
|
An asn1crypto.x509.Certificate object X.509 end-entity
|
|
certificate to validate
|
|
|
|
:param intermediate_certs:
|
|
None or a list of asn1crypto.x509.Certificate
|
|
Used in constructing certificate paths for validation.
|
|
|
|
:param validation_context:
|
|
A pyhanko_certvalidator.context.ValidationContext() object that
|
|
controls generic validation options and tracks revocation data.
|
|
|
|
The same validation context will also be used in the validation
|
|
of relevant certificates found in OCSP responses and/or CRLs.
|
|
|
|
:param pkix_params:
|
|
A pyhanko_certvalidator.context.PKIXValidationParams() object that
|
|
controls advanced PKIX validation parameters used to validate
|
|
the end-entity certificate. These can be used to constrain
|
|
policy processing and names.
|
|
|
|
Ancillary validation of CRLs and OCSP responses ignore these
|
|
settings.
|
|
"""
|
|
|
|
if validation_context is None:
|
|
validation_context = ValidationContext()
|
|
|
|
if intermediate_certs is not None:
|
|
certificate_registry = validation_context.certificate_registry
|
|
for intermediate_cert in intermediate_certs:
|
|
certificate_registry.register(intermediate_cert)
|
|
|
|
self._context: ValidationContext = validation_context
|
|
self._certificate: x509.Certificate = end_entity_cert
|
|
self._params: Optional[PKIXValidationParams] = pkix_params
|
|
|
|
@property
|
|
def certificate(self):
|
|
return self._certificate
|
|
|
|
async def async_validate_path(self) -> ValidationPath:
|
|
"""
|
|
Builds possible certificate paths and validates them until a valid one
|
|
is found, or all fail.
|
|
|
|
:raises:
|
|
pyhanko_certvalidator.errors.PathBuildingError - when an error occurs building the path
|
|
pyhanko_certvalidator.errors.PathValidationError - when an error occurs validating the path
|
|
pyhanko_certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
|
|
"""
|
|
|
|
if self._path is not None:
|
|
return self._path
|
|
|
|
certificate = self._certificate
|
|
|
|
paths = self._context.path_builder.async_build_paths_lazy(certificate)
|
|
self._path = candidate_path = await find_valid_path(
|
|
certificate,
|
|
paths,
|
|
validation_context=self._context,
|
|
pkix_validation_params=self._params,
|
|
)
|
|
return candidate_path
|
|
|
|
def validate_usage(
|
|
self, key_usage, extended_key_usage=None, extended_optional=False
|
|
):
|
|
"""
|
|
Validates the certificate path and that the certificate is valid for
|
|
the key usage and extended key usage purposes specified.
|
|
|
|
.. deprecated:: 0.17.0
|
|
Use :meth:`async_validate_usage` instead.
|
|
|
|
:param key_usage:
|
|
A set of unicode strings of the required key usage purposes. Valid
|
|
values include:
|
|
|
|
- "digital_signature"
|
|
- "non_repudiation"
|
|
- "key_encipherment"
|
|
- "data_encipherment"
|
|
- "key_agreement"
|
|
- "key_cert_sign"
|
|
- "crl_sign"
|
|
- "encipher_only"
|
|
- "decipher_only"
|
|
|
|
:param extended_key_usage:
|
|
A set of unicode strings of the required extended key usage
|
|
purposes. These must be either dotted number OIDs, or one of the
|
|
following extended key usage purposes:
|
|
|
|
- "server_auth"
|
|
- "client_auth"
|
|
- "code_signing"
|
|
- "email_protection"
|
|
- "ipsec_end_system"
|
|
- "ipsec_tunnel"
|
|
- "ipsec_user"
|
|
- "time_stamping"
|
|
- "ocsp_signing"
|
|
- "wireless_access_points"
|
|
|
|
An example of a dotted number OID:
|
|
|
|
- "1.3.6.1.5.5.7.3.1"
|
|
|
|
:param extended_optional:
|
|
A bool - if the extended_key_usage extension may be ommited and still
|
|
considered valid
|
|
|
|
:raises:
|
|
pyhanko_certvalidator.errors.PathValidationError - when an error occurs validating the path
|
|
pyhanko_certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
|
|
pyhanko_certvalidator.errors.InvalidCertificateError - when the certificate is not valid for the usages specified
|
|
|
|
:return:
|
|
A pyhanko_certvalidator.path.ValidationPath object of the validated
|
|
certificate validation path
|
|
"""
|
|
|
|
warnings.warn(
|
|
"'validate_usage' is deprecated, use "
|
|
"'async_validate_usage' instead",
|
|
DeprecationWarning,
|
|
)
|
|
|
|
return asyncio.run(
|
|
self.async_validate_usage(
|
|
key_usage, extended_key_usage, extended_optional
|
|
)
|
|
)
|
|
|
|
async def async_validate_usage(
|
|
self, key_usage, extended_key_usage=None, extended_optional=False
|
|
):
|
|
"""
|
|
Validates the certificate path and that the certificate is valid for
|
|
the key usage and extended key usage purposes specified.
|
|
|
|
:param key_usage:
|
|
A set of unicode strings of the required key usage purposes. Valid
|
|
values include:
|
|
|
|
- "digital_signature"
|
|
- "non_repudiation"
|
|
- "key_encipherment"
|
|
- "data_encipherment"
|
|
- "key_agreement"
|
|
- "key_cert_sign"
|
|
- "crl_sign"
|
|
- "encipher_only"
|
|
- "decipher_only"
|
|
|
|
:param extended_key_usage:
|
|
A set of unicode strings of the required extended key usage
|
|
purposes. These must be either dotted number OIDs, or one of the
|
|
following extended key usage purposes:
|
|
|
|
- "server_auth"
|
|
- "client_auth"
|
|
- "code_signing"
|
|
- "email_protection"
|
|
- "ipsec_end_system"
|
|
- "ipsec_tunnel"
|
|
- "ipsec_user"
|
|
- "time_stamping"
|
|
- "ocsp_signing"
|
|
- "wireless_access_points"
|
|
|
|
An example of a dotted number OID:
|
|
|
|
- "1.3.6.1.5.5.7.3.1"
|
|
|
|
:param extended_optional:
|
|
A bool - if the extended_key_usage extension may be ommited and still
|
|
considered valid
|
|
|
|
:raises:
|
|
pyhanko_certvalidator.errors.PathValidationError - when an error occurs validating the path
|
|
pyhanko_certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
|
|
pyhanko_certvalidator.errors.InvalidCertificateError - when the certificate is not valid for the usages specified
|
|
|
|
:return:
|
|
A pyhanko_certvalidator.path.ValidationPath object of the validated
|
|
certificate validation path
|
|
"""
|
|
|
|
validated_path = await self.async_validate_path()
|
|
validate_usage(
|
|
self._context,
|
|
self._certificate,
|
|
key_usage,
|
|
extended_key_usage,
|
|
extended_optional,
|
|
)
|
|
return validated_path
|
|
|
|
def validate_tls(self, hostname):
|
|
"""
|
|
Validates the certificate path, that the certificate is valid for
|
|
the hostname provided and that the certificate is valid for the purpose
|
|
of a TLS connection.
|
|
|
|
.. deprecated:: 0.17.0
|
|
Use :meth:`async_validate_tls` instead.
|
|
|
|
:param hostname:
|
|
A unicode string of the TLS server hostname
|
|
|
|
:raises:
|
|
pyhanko_certvalidator.errors.PathValidationError - when an error occurs validating the path
|
|
pyhanko_certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
|
|
pyhanko_certvalidator.errors.InvalidCertificateError - when the certificate is not valid for TLS or the hostname
|
|
|
|
:return:
|
|
A pyhanko_certvalidator.path.ValidationPath object of the validated
|
|
certificate validation path
|
|
"""
|
|
|
|
warnings.warn(
|
|
"'validate_tls' is deprecated, use 'async_validate_tls' instead",
|
|
DeprecationWarning,
|
|
)
|
|
|
|
return asyncio.run(self.async_validate_tls(hostname))
|
|
|
|
async def async_validate_tls(self, hostname):
|
|
"""
|
|
Validates the certificate path, that the certificate is valid for
|
|
the hostname provided and that the certificate is valid for the purpose
|
|
of a TLS connection.
|
|
|
|
:param hostname:
|
|
A unicode string of the TLS server hostname
|
|
|
|
:raises:
|
|
pyhanko_certvalidator.errors.PathValidationError - when an error occurs validating the path
|
|
pyhanko_certvalidator.errors.RevokedError - when the certificate or another certificate in its path has been revoked
|
|
pyhanko_certvalidator.errors.InvalidCertificateError - when the certificate is not valid for TLS or the hostname
|
|
|
|
:return:
|
|
A pyhanko_certvalidator.path.ValidationPath object of the validated
|
|
certificate validation path
|
|
"""
|
|
|
|
await self.async_validate_path()
|
|
validate_tls_hostname(self._context, self._certificate, hostname)
|
|
return self._path
|