diff --git a/NEWS.rst b/NEWS.rst index cad4e825f3..a660de21ed 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -9,6 +9,70 @@ .. towncrier release notes start +26.0.1.post1 (2026-10-21) +========================= + +Security Fixes +-------------- + +- **CVE-2026-3219**: Disambiguate archive type detection in ``unpack_file`` + so a tar+zip polyglot is no longer always treated as a zip; ambiguous + archives are rejected. (`GHSA-58qw-9mgm-455v `_) +- **CVE-2026-6357**: Run the network and import side of the pip + self-version check before the command body, so a just-installed wheel + cannot shadow a module the check imports afterwards; only the upgrade + prompt is printed after the command. (`GHSA-jp4c-xjxw-mgf9 `_) +- **CVE-2026-8643**: Reject ``console_scripts`` and ``gui_scripts`` entry + points whose name would install a script outside the scripts directory. + The vendored distlib 0.4.0 accepts such names unchanged, so this release + was directly affected. (`GHSA-wf93-45jw-7689 `_) +- **CVE-2026-13346**: Stop ``Link.filename`` percent-decoding the URL path a + second time, so a doubly-encoded separator from a malicious index (e.g. + ``..%252F..%252Fevil.whl``) can no longer write a download outside the + download directory. Download file names are now always validated as a + single path component. (`GHSA-qwm4-qh6w-59xr `_) +- **GHSA-6q48-6c7w-9gw2**: Upgrade vendored certifi from 2026.1.4 to + 2026.10.19 (the release vendored by pip 26.2.2), dropping the emSign ECC + Root CA - G3, whose intermediate CA keys were compromised. (`GHSA-6q48-6c7w-9gw2 `_) +- **CVE-2025-8869**: Harden the tar extraction fallback (used when + ``tarfile.data_filter`` is unavailable, i.e. Python 3.9.0-3.9.16), porting + pypa/pip#14127: members are also checked after resolving symlinks, and a + symlink that resolves outside the destination is rejected before it is + created. Previously a symlink to ``.`` or ``..`` let a later member be + written outside the extract directory. (`GHSA-4xh5-x5gv-qwph `_) + +Bug Fixes +--------- + +- ``is_within_directory`` no longer uses ``os.path.commonpath``, so installs + under a POSIX ``//``-prefixed path (e.g. ``--prefix //tmp/x``) work again, + and a directory at a filesystem root (``/`` or ``C:\``) is handled; a + target on another Windows drive is reported as outside instead of raising. +- A scripts directory at a filesystem root no longer rejects every console + or GUI script (the CVE-2026-8643 check now uses ``is_within_directory``). +- The tar fallback's symlink check compares normalized member names, so + ``./``-prefixed archives and links to a directory that has no entry of its + own unpack again; containment is still enforced by the symlink-resolving + checks above. +- An unsafe download file name (e.g. ``Content-Disposition: filename=".."``) + is reported as a pip ``ERROR:`` instead of a ``ValueError`` traceback. +- The ``pip install pip`` detection used by the self-version check no longer + fails on a requirement that makes packaging raise a bare ``ValueError``. + +.. note:: + + CVE-2026-1703 / GHSA-6vgw-5pg2-w6jp (``is_within_directory`` prefix + comparison) was already fixed upstream before pip 26.0.1. + CVE-2025-8869 / GHSA-4xh5-x5gv-qwph (tar symlink targets on the + extraction fallback) was fixed in 26.0.1 and is hardened in this release + (see above). + + This is an ActiveState security release for Python 3.9, the last Python + version pip 26.0.x supports (pip 26.1 requires Python 3.10). It uses the + PEP 440 post-release scheme: ``26.0.1.post1`` sorts after ``26.0.1`` and + before ``26.0.2``. + + 26.0.1 (2026-02-04) =================== diff --git a/src/pip/__init__.py b/src/pip/__init__.py index 978b412920..0a50fed5a6 100644 --- a/src/pip/__init__.py +++ b/src/pip/__init__.py @@ -1,6 +1,6 @@ from __future__ import annotations -__version__ = "26.0.1" +__version__ = "26.0.1.post1" def main(args: list[str] | None = None) -> int: diff --git a/src/pip/_internal/cli/base_command.py b/src/pip/_internal/cli/base_command.py index 499a46f964..fa7694d7c7 100644 --- a/src/pip/_internal/cli/base_command.py +++ b/src/pip/_internal/cli/base_command.py @@ -2,12 +2,14 @@ from __future__ import annotations +import contextlib import logging import logging.config import optparse import os import sys import traceback +from collections.abc import Iterator from optparse import Values from typing import Callable @@ -80,7 +82,8 @@ def __init__(self, name: str, summary: str, isolated: bool = False) -> None: def add_options(self) -> None: pass - def handle_pip_version_check(self, options: Values) -> None: + @contextlib.contextmanager + def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: """ This is a no-op so that commands by default do not do the pip version check. @@ -88,16 +91,15 @@ def handle_pip_version_check(self, options: Values) -> None: # Make sure we do the pip version check if the index_group options # are present. assert not hasattr(options, "no_index") + yield def run(self, options: Values, args: list[str]) -> int: raise NotImplementedError def _run_wrapper(self, level_number: int, options: Values, args: list[str]) -> int: def _inner_run() -> int: - try: + with self.pip_version_check(options, args): return self.run(options, args) - finally: - self.handle_pip_version_check(options) if options.debug_mode: rich_traceback.install(show_locals=True) diff --git a/src/pip/_internal/cli/index_command.py b/src/pip/_internal/cli/index_command.py index 0f1ee1a4f8..ee07066497 100644 --- a/src/pip/_internal/cli/index_command.py +++ b/src/pip/_internal/cli/index_command.py @@ -8,9 +8,11 @@ from __future__ import annotations +import contextlib import logging import os import sys +from collections.abc import Iterator from functools import lru_cache from optparse import Values from typing import TYPE_CHECKING @@ -26,6 +28,7 @@ from pip._vendor.packaging.utils import NormalizedName from pip._internal.network.session import PipSession + from pip._internal.self_outdated_check import UpgradePrompt logger = logging.getLogger(__name__) @@ -139,10 +142,18 @@ def _build_session( return session -def _pip_self_version_check(session: PipSession, options: Values) -> None: - from pip._internal.self_outdated_check import pip_self_version_check as check +def _pip_self_version_check_fetch( + session: PipSession, options: Values +) -> UpgradePrompt | None: + from pip._internal.self_outdated_check import pip_self_version_check_fetch - check(session, options) + return pip_self_version_check_fetch(session, options) + + +def _pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: + from pip._internal.self_outdated_check import pip_self_version_check_emit + + pip_self_version_check_emit(upgrade_prompt) class IndexGroupCommand(Command, SessionCommandMixin): @@ -169,7 +180,8 @@ def should_exclude_prerelease( # No specific setting: exclude prereleases by default return True - def handle_pip_version_check(self, options: Values) -> None: + @contextlib.contextmanager + def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: """ Do the pip version check if not disabled. @@ -179,17 +191,27 @@ def handle_pip_version_check(self, options: Values) -> None: assert hasattr(options, "no_index") if options.disable_pip_version_check or options.no_index: + yield return + upgrade_prompt: UpgradePrompt | None = None try: - # Otherwise, check if we're using the latest version of pip available. session = self._build_session( options, retries=0, timeout=min(5, options.timeout), ) with session: - _pip_self_version_check(session, options) + upgrade_prompt = _pip_self_version_check_fetch(session, options) except Exception: logger.warning("There was an error checking the latest version of pip.") logger.debug("See below for error", exc_info=True) + + try: + yield + finally: + try: + _pip_self_version_check_emit(upgrade_prompt) + except Exception: + logger.warning("There was an error checking the latest version of pip.") + logger.debug("See below for error", exc_info=True) diff --git a/src/pip/_internal/commands/install.py b/src/pip/_internal/commands/install.py index 8c3d86648b..2abf72ab2e 100644 --- a/src/pip/_internal/commands/install.py +++ b/src/pip/_internal/commands/install.py @@ -1,14 +1,17 @@ from __future__ import annotations +import contextlib import errno import json import operator import os import shutil import site +from collections.abc import Iterator from optparse import SUPPRESS_HELP, Values from pathlib import Path +from pip._vendor.packaging.requirements import InvalidRequirement, Requirement from pip._vendor.packaging.utils import canonicalize_name from pip._vendor.requests.exceptions import InvalidProxyURL from pip._vendor.rich import print_json @@ -63,6 +66,16 @@ logger = getLogger(__name__) +def _arg_refers_to_pip(arg: str) -> bool: + try: + req = Requirement(arg) + except (InvalidRequirement, ValueError): + # Older packaging can surface a bare ValueError (e.g. from urlsplit on + # "foo @ http://[bad"); let pip's normal requirement parsing report it. + return False + return canonicalize_name(req.name) == "pip" + + class InstallCommand(RequirementCommand): """ Install packages from: @@ -278,6 +291,17 @@ def add_options(self) -> None: ), ) + @contextlib.contextmanager + def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: + # Skip the self-version check when pip itself is a requirement. The + # running pip may be replaced mid-command, and the upgrade prompt + # is redundant. + if any(_arg_refers_to_pip(arg) for arg in args): + yield + return + with super().pip_version_check(options, args): + yield + @with_cleanup def run(self, options: Values, args: list[str]) -> int: if options.use_user_site and options.target_dir is not None: diff --git a/src/pip/_internal/commands/list.py b/src/pip/_internal/commands/list.py index f9bad7e0d2..bc8a5696ba 100644 --- a/src/pip/_internal/commands/list.py +++ b/src/pip/_internal/commands/list.py @@ -1,8 +1,9 @@ from __future__ import annotations +import contextlib import json import logging -from collections.abc import Generator, Sequence +from collections.abc import Generator, Iterator, Sequence from email.parser import Parser from optparse import Values from typing import TYPE_CHECKING, cast @@ -135,9 +136,13 @@ def add_options(self) -> None: self.parser.insert_option_group(0, selection_opts) self.parser.insert_option_group(0, self.cmd_opts) - def handle_pip_version_check(self, options: Values) -> None: - if options.outdated or options.uptodate: - super().handle_pip_version_check(options) + @contextlib.contextmanager + def pip_version_check(self, options: Values, args: list[str]) -> Iterator[None]: + if not (options.outdated or options.uptodate): + yield + return + with super().pip_version_check(options, args): + yield def _build_package_finder( self, options: Values, session: PipSession diff --git a/src/pip/_internal/exceptions.py b/src/pip/_internal/exceptions.py index 9ddda0e626..31fe45929f 100644 --- a/src/pip/_internal/exceptions.py +++ b/src/pip/_internal/exceptions.py @@ -192,6 +192,10 @@ class InstallationError(PipError): """General exception during installation""" +class UnsafeFilenameError(InstallationError, ValueError): + """A file name derived from a URL or header is not a single safe path component.""" + + class FailedToPrepareCandidate(InstallationError): """Raised when we fail to prepare a candidate (i.e. fetch and generate metadata). diff --git a/src/pip/_internal/models/link.py b/src/pip/_internal/models/link.py index 200ec34c56..d64f747c9d 100644 --- a/src/pip/_internal/models/link.py +++ b/src/pip/_internal/models/link.py @@ -14,9 +14,10 @@ from typing import ( Any, NamedTuple, + NewType, ) -from pip._internal.exceptions import InvalidEggFragment +from pip._internal.exceptions import InvalidEggFragment, UnsafeFilenameError from pip._internal.utils.datetime import parse_iso_datetime from pip._internal.utils.filetypes import WHEEL_EXTENSION from pip._internal.utils.hashes import Hashes @@ -31,6 +32,49 @@ logger = logging.getLogger(__name__) +# A single path component: percent-decoded once and reduced to a basename, so it +# contains no path separator and is not a ``.`` or ``..`` reference. The empty +# string means "no component". +PathComponent = NewType("PathComponent", str) + + +def _to_path_component(name: str) -> PathComponent: + """Reduce ``name`` to a single path component, or ``""`` if it has none. + + ``os.path.basename`` drops any directory part, drive letter, or separator; + a ``.``, ``..``, or empty result is not a component and becomes ``""``. + """ + name = os.path.basename(name) + if name in ("", os.curdir, os.pardir): + return PathComponent("") + + return PathComponent(name) + + +def as_path_component(name: str) -> PathComponent: + """Like ``_to_path_component`` but reject the empty result. + + Use where a file is about to be written, so a missing name is an error + rather than a silent fallback to the directory itself. + """ + component = _to_path_component(name) + if not component: + raise UnsafeFilenameError(f"Unexpected file name derived from URL: {name!r}") + + return component + + +def join_within_directory(directory: str, component: PathComponent) -> str: + """Join a single path ``component`` onto ``directory``. + + ``component`` is a :data:`PathComponent`, so by type it has no separator and + is not a ``.`` or ``..`` reference; the result can never escape ``directory``. + Requiring ``PathComponent`` rather than ``str`` lets the type checker enforce + at the call site that the name was reduced to a safe component beforehand. + """ + return os.path.join(directory, component) + + # Order matters, earlier hashes have a precedence over later hashes for what # we will pick to use. _SUPPORTED_HASHES = ("sha512", "sha384", "sha256", "sha224", "sha1", "md5") @@ -423,18 +467,13 @@ def redacted_url(self) -> str: return redact_auth_from_url(self.url) @property - def filename(self) -> str: - path = self.path.rstrip("/") - name = posixpath.basename(path) - if not name: - # Make sure we don't leak auth information if the netloc - # includes a username and password. - netloc, user_pass = split_auth_from_netloc(self.netloc) - return netloc - - name = urllib.parse.unquote(name) - assert name, f"URL {self._url!r} produced no filename" - return name + def filename(self) -> PathComponent: + name = _to_path_component(posixpath.basename(self.path.rstrip("/"))) + if name: + return name + + # No component in the path; fall back to the netloc, dropping any auth. + return _to_path_component(split_auth_from_netloc(self.netloc)[0]) @property def file_path(self) -> str: diff --git a/src/pip/_internal/network/download.py b/src/pip/_internal/network/download.py index 26966423f6..fa71c75a38 100644 --- a/src/pip/_internal/network/download.py +++ b/src/pip/_internal/network/download.py @@ -20,7 +20,12 @@ from pip._internal.cli.progress_bars import BarType, get_download_progress_renderer from pip._internal.exceptions import IncompleteDownloadError, NetworkConnectionError from pip._internal.models.index import PyPI -from pip._internal.models.link import Link +from pip._internal.models.link import ( + Link, + PathComponent, + as_path_component, + join_within_directory, +) from pip._internal.network.cache import SafeFileCache, is_from_cache from pip._internal.network.session import CacheControlAdapter, PipSession from pip._internal.network.utils import HEADERS, raise_for_status, response_chunks @@ -117,11 +122,14 @@ def parse_content_disposition(content_disposition: str, default_filename: str) - return filename or default_filename -def _get_http_response_filename(resp: Response, link: Link) -> str: +def _get_http_response_filename(resp: Response, link: Link) -> PathComponent: """Get an ideal filename from the given HTTP response, falling back to the link filename if not provided. + + The result is validated as a single path component, so it can be joined onto + a download directory without escaping it. """ - filename = link.filename # fallback + filename: str = link.filename # fallback # Have a look at the Content-Disposition header for a better guess content_disposition = resp.headers.get("content-disposition") if content_disposition: @@ -135,7 +143,7 @@ def _get_http_response_filename(resp: Response, link: Link) -> str: ext = os.path.splitext(resp.url)[1] if ext: filename += ext - return filename + return as_path_component(filename) @dataclass @@ -188,7 +196,9 @@ def __call__(self, link: Link, location: str) -> tuple[str, str]: resp = self._http_get(link) download_size = _get_http_response_size(resp) - filepath = os.path.join(location, _get_http_response_filename(resp, link)) + filepath = join_within_directory( + location, _get_http_response_filename(resp, link) + ) with open(filepath, "wb") as content_file: download = _FileDownload(link, content_file, download_size) self._process_response(download, resp) diff --git a/src/pip/_internal/operations/install/wheel.py b/src/pip/_internal/operations/install/wheel.py index 40097d6a79..9676166764 100644 --- a/src/pip/_internal/operations/install/wheel.py +++ b/src/pip/_internal/operations/install/wheel.py @@ -397,11 +397,32 @@ def __init__(self, entry_point: str) -> None: ) -def _raise_for_invalid_entrypoint(specification: str) -> None: +def _script_within_dir(name: str, scripts_dir: str) -> bool: + """Return whether script ``name`` resolves to a path inside the ``scripts_dir``. + + distlib joins the entry point name onto the scripts directory, so a name + with path separators or ``..`` components can resolve elsewhere. + """ + root = os.path.normpath(scripts_dir) + dest = os.path.normpath(os.path.join(scripts_dir, name)) + # The script must be a file *inside* the directory, never the directory itself. + return dest != root and is_within_directory(root, dest) + + +def _raise_for_invalid_entrypoint(specification: str, scripts_dir: str) -> None: entry = get_export_entry(specification) - if entry is not None and entry.suffix is None: + if entry is None: + return + + if entry.suffix is None: raise MissingCallableSuffix(str(entry)) + if not _script_within_dir(entry.name, scripts_dir): + raise InstallationError( + f"Invalid script entry point name {entry.name!r}: the script " + f"would be installed outside the scripts directory ({scripts_dir})." + ) + class PipScriptMaker(ScriptMaker): # Override distlib's default script template with one that @@ -419,7 +440,7 @@ class PipScriptMaker(ScriptMaker): def make( self, specification: str, options: dict[str, Any] | None = None ) -> list[str]: - _raise_for_invalid_entrypoint(specification) + _raise_for_invalid_entrypoint(specification, self.target_dir) return super().make(specification, options) diff --git a/src/pip/_internal/operations/prepare.py b/src/pip/_internal/operations/prepare.py index 67f9ee9504..d260d15a28 100644 --- a/src/pip/_internal/operations/prepare.py +++ b/src/pip/_internal/operations/prepare.py @@ -29,7 +29,7 @@ from pip._internal.index.package_finder import PackageFinder from pip._internal.metadata import BaseDistribution, get_metadata_distribution from pip._internal.models.direct_url import ArchiveInfo -from pip._internal.models.link import Link +from pip._internal.models.link import Link, join_within_directory from pip._internal.models.wheel import Wheel from pip._internal.network.download import Downloader from pip._internal.network.lazy_wheel import ( @@ -201,7 +201,7 @@ def _check_download_dir( """Check download_dir for previously downloaded file with correct hash If a correct file is found return its path else None """ - download_path = os.path.join(download_dir, link.filename) + download_path = join_within_directory(download_dir, link.filename) if not os.path.exists(download_path): return None @@ -683,7 +683,7 @@ def save_linked_requirement(self, req: InstallRequirement) -> None: # No distribution was downloaded for this requirement. return - download_location = os.path.join(self.download_dir, link.filename) + download_location = join_within_directory(self.download_dir, link.filename) if not os.path.exists(download_location): shutil.copy(req.local_file_path, download_location) download_path = display_path(download_location) diff --git a/src/pip/_internal/self_outdated_check.py b/src/pip/_internal/self_outdated_check.py index e131ec8f84..4b5248ed3d 100644 --- a/src/pip/_internal/self_outdated_check.py +++ b/src/pip/_internal/self_outdated_check.py @@ -1,7 +1,6 @@ from __future__ import annotations import datetime -import functools import hashlib import json import logging @@ -9,7 +8,6 @@ import os.path import sys from dataclasses import dataclass -from typing import Callable from pip._vendor.packaging.version import Version from pip._vendor.packaging.version import parse as parse_version @@ -156,16 +154,6 @@ def __rich__(self) -> Group: ) -def was_installed_by_pip(pkg: str) -> bool: - """Checks whether pkg was installed by pip - - This is used not to display the upgrade message when pip is in fact - installed by system package manager, such as dnf on Fedora. - """ - dist = get_default_environment().get_distribution(pkg) - return dist is not None and "pip" == dist.installer - - def _get_current_remote_pip_version( session: PipSession, options: optparse.Values ) -> str | None: @@ -194,28 +182,15 @@ def _get_current_remote_pip_version( return str(best_candidate.version) -def _self_version_check_logic( - *, - state: SelfCheckState, - current_time: datetime.datetime, - local_version: Version, - get_remote_version: Callable[[], str | None], +def _compute_upgrade_prompt( + local_version: Version, remote_version_str: str, installed_by_pip: bool ) -> UpgradePrompt | None: - remote_version_str = state.get(current_time) - if remote_version_str is None: - remote_version_str = get_remote_version() - if remote_version_str is None: - logger.debug("No remote pip version found") - return None - state.set(remote_version_str, current_time) - remote_version = parse_version(remote_version_str) logger.debug("Remote version of pip: %s", remote_version) logger.debug("Local version of pip: %s", local_version) + logger.debug("Was pip installed by pip? %s", installed_by_pip) - pip_installed_by_pip = was_installed_by_pip("pip") - logger.debug("Was pip installed by pip? %s", pip_installed_by_pip) - if not pip_installed_by_pip: + if not installed_by_pip: return None # Only suggest upgrade if pip is installed by pip. local_version_is_older = ( @@ -228,28 +203,44 @@ def _self_version_check_logic( return None -def pip_self_version_check(session: PipSession, options: optparse.Values) -> None: - """Check for an update for pip. +def pip_self_version_check_fetch( + session: PipSession, options: optparse.Values +) -> UpgradePrompt | None: + """Compute the pip upgrade prompt, if any, before the command runs. Limit the frequency of checks to once per week. State is stored either in the active virtualenv or in the user's USER_CACHE_DIR keyed off the prefix of the pip script path. + + Pair with :func:`pip_self_version_check_emit`, which displays the prompt + after the command body runs. """ installed_dist = get_default_environment().get_distribution("pip") if not installed_dist: - return + return None try: check_externally_managed() except ExternallyManagedEnvironment: - return + return None + + state = SelfCheckState(cache_dir=options.cache_dir) + current_time = datetime.datetime.now(datetime.timezone.utc) + remote_version_str = state.get(current_time) + if remote_version_str is None: + remote_version_str = _get_current_remote_pip_version(session, options) + if remote_version_str is None: + logger.debug("No remote pip version found") + return None + state.set(remote_version_str, current_time) - upgrade_prompt = _self_version_check_logic( - state=SelfCheckState(cache_dir=options.cache_dir), - current_time=datetime.datetime.now(datetime.timezone.utc), + return _compute_upgrade_prompt( local_version=installed_dist.version, - get_remote_version=functools.partial( - _get_current_remote_pip_version, session, options - ), + remote_version_str=remote_version_str, + installed_by_pip=installed_dist.installer == "pip", ) + + +def pip_self_version_check_emit(upgrade_prompt: UpgradePrompt | None) -> None: + """Emit the upgrade prompt captured by :func:`pip_self_version_check_fetch`.""" if upgrade_prompt is not None: logger.warning("%s", upgrade_prompt, extra={"rich": True}) diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py index b3f52e85e6..8c2ac328b0 100644 --- a/src/pip/_internal/utils/unpacking.py +++ b/src/pip/_internal/utils/unpacking.py @@ -4,6 +4,7 @@ import logging import os +import posixpath import shutil import stat import sys @@ -76,15 +77,30 @@ def has_leading_dir(paths: Iterable[str]) -> bool: return True -def is_within_directory(directory: str, target: str) -> bool: +def is_within_directory( + directory: str, target: str, *, resolve_symlinks: bool = False +) -> bool: """ Return true if the absolute path of target is within the directory + (including when target is equal to the directory). + + When ``resolve_symlinks`` is true, resolve symlinks before comparing so + traversal through a symlink (e.g. "link/../file") is also caught. """ - abs_directory = os.path.abspath(directory) - abs_target = os.path.abspath(target) + if resolve_symlinks: + abs_directory = os.path.realpath(directory) + abs_target = os.path.realpath(target) + else: + abs_directory = os.path.abspath(directory) + abs_target = os.path.abspath(target) - prefix = os.path.commonpath([abs_directory, abs_target]) - return prefix == abs_directory + if abs_target == abs_directory: + return True + # A filesystem root ("/" or "C:\\") already ends with a separator; appending + # another would reject everything. abspath keeps a POSIX "//" prefix, which + # os.path.commonpath would drop. + prefix = abs_directory if abs_directory.endswith(os.sep) else abs_directory + os.sep + return abs_target.startswith(prefix) def _get_default_mode_plus_executable() -> int: @@ -248,18 +264,39 @@ def pip_filter(member: tarfile.TarInfo, path: str) -> tarfile.TarInfo: tar.close() -def is_symlink_target_in_tar(tar: tarfile.TarFile, tarinfo: tarfile.TarInfo) -> bool: - """Check if the file pointed to by the symbolic link is in the tar archive""" +def _tar_member_paths(tar: tarfile.TarFile) -> set[str]: + """Normalized member paths plus every implied parent directory.""" + paths = set() + for member in tar.getmembers(): + name = posixpath.normpath(member.name.replace("\\", "/")) + while name not in ("", ".", "/"): + paths.add(name) + parent = posixpath.dirname(name) + if parent == name: + # posixpath.dirname("//") == "//": stop at any fixed point. + break + name = parent + return paths + + +def is_symlink_target_in_tar( + tar: tarfile.TarFile, + tarinfo: tarfile.TarInfo, + *, + member_paths: set[str] | None = None, +) -> bool: + """Check if the file pointed to by the symbolic link is in the tar archive + + Member names are compared after normalization, so "./"-prefixed archives + and links to a directory that only exists implicitly (as the parent of + other members) are accepted. Containment itself is enforced separately by + the realpath checks in _untar_without_filter. + """ linkname = os.path.join(os.path.dirname(tarinfo.name), tarinfo.linkname) - - linkname = os.path.normpath(linkname) - linkname = linkname.replace("\\", "/") - - try: - tar.getmember(linkname) - return True - except KeyError: - return False + linkname = posixpath.normpath(linkname.replace("\\", "/")) + if member_paths is None: + member_paths = _tar_member_paths(tar) + return linkname in member_paths def _untar_without_filter( @@ -272,12 +309,19 @@ def _untar_without_filter( # NOTE: This function can be removed once pip requires CPython ≥ 3.12.​ # PEP 706 added tarfile.data_filter, made tarfile extraction operations more secure. # This feature is fully supported from CPython 3.12 onward. + member_paths = _tar_member_paths(tar) for member in tar.getmembers(): fn = member.name if leading: fn = split_leading_dir(fn)[1] path = os.path.join(location, fn) - if not is_within_directory(location, path): + + # The plain check rejects textual ".." escapes; resolving symlinks also + # catches a later member redirected outside by an earlier member's + # symlink (e.g. "link/../file"). + if not is_within_directory(location, path) or not is_within_directory( + location, path, resolve_symlinks=True + ): message = ( "The tar file ({}) has a file ({}) trying to install " "outside target directory ({})" @@ -286,7 +330,18 @@ def _untar_without_filter( if member.isdir(): ensure_dir(path) elif member.issym(): - if not is_symlink_target_in_tar(tar, member): + # Reject symlinks resolving outside the destination, so a later + # member cannot be written through them. + target = os.path.join(os.path.dirname(path), member.linkname) + if not is_within_directory(location, target, resolve_symlinks=True): + message = ( + "The tar file ({}) has a file ({}) trying to install " + "outside target directory ({})" + ) + raise InstallationError( + message.format(filename, member.name, member.linkname) + ) + if not is_symlink_target_in_tar(tar, member, member_paths=member_paths): message = ( "The tar file ({}) has a file ({}) trying to install " "outside target directory ({})" @@ -336,27 +391,46 @@ def unpack_file( location: str, content_type: str | None = None, ) -> None: + """Unpack ``filename`` into ``location``. + + Archive format is chosen in order of decreasing reliability: + ``content_type``, then filename extension, then magic signature + (unambiguous matches only). + """ filename = os.path.realpath(filename) - if ( - content_type == "application/zip" - or filename.lower().endswith(ZIP_EXTENSIONS) - or zipfile.is_zipfile(filename) - ): - unzip_file(filename, location, flatten=not filename.endswith(".whl")) - elif ( - content_type == "application/x-gzip" - or tarfile.is_tarfile(filename) - or filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS) - ): + zip_flatten = not filename.endswith(".whl") + + def _unzip() -> None: + unzip_file(filename, location, flatten=zip_flatten) + + def _untar() -> None: untar_file(filename, location) - else: - # FIXME: handle? - # FIXME: magic signatures? - logger.critical( - "Cannot unpack file %s (downloaded from %s, content-type: %s); " - "cannot detect archive format", - filename, - location, - content_type, - ) - raise InstallationError(f"Cannot determine archive format of {location}") + + if content_type == "application/zip": + return _unzip() + if content_type == "application/x-gzip": + return _untar() + + if filename.lower().endswith(ZIP_EXTENSIONS): + return _unzip() + if filename.lower().endswith(TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS): + return _untar() + + # avoid ambiguous case where both signature checks return True + is_zipfile = zipfile.is_zipfile(filename) + is_tarfile = tarfile.is_tarfile(filename) + if is_zipfile and not is_tarfile: + return _unzip() + if is_tarfile and not is_zipfile: + return _untar() + if is_zipfile and is_tarfile: + logger.error("Ambiguous file signature in %s.", filename) + + logger.critical( + "Cannot unpack file %s (downloaded from %s, content-type: %s); " + "cannot detect archive format", + filename, + location, + content_type, + ) + raise InstallationError(f"Cannot determine archive format of {location}") diff --git a/src/pip/_vendor/certifi/__init__.py b/src/pip/_vendor/certifi/__init__.py index 090fd58487..5e43134634 100644 --- a/src/pip/_vendor/certifi/__init__.py +++ b/src/pip/_vendor/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2026.01.04" +__version__ = "2026.10.19" diff --git a/src/pip/_vendor/certifi/cacert.pem b/src/pip/_vendor/certifi/cacert.pem index 132db0df1b..156dc85188 100644 --- a/src/pip/_vendor/certifi/cacert.pem +++ b/src/pip/_vendor/certifi/cacert.pem @@ -1,316 +1,4 @@ -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln -biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF -MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT -d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 -76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ -bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c -6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE -emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd -MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt -MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y -MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y -FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi -aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM -gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB -qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 -lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn -8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov -L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 -45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO -UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 -O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC -bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv -GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a -77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC -hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 -92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp -Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w -ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt -Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - # Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited # Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited # Label: "COMODO ECC Certification Authority" @@ -335,105 +23,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - # Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) # Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" @@ -1097,44 +686,6 @@ aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - # Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center # Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center # Label: "T-TeleSec GlobalRoot Class 2" @@ -1166,35 +717,6 @@ g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN BSeOE6Fuwg== -----END CERTIFICATE----- -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - # Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited # Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited # Label: "QuoVadis Root CA 1 G3" @@ -2483,29 +2005,6 @@ RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx iN66zB+Afko= -----END CERTIFICATE----- -# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign ECC Root CA - G3" -# Serial: 287880440101571086945156 -# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 -# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 -# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b ------BEGIN CERTIFICATE----- -MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG -EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo -bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g -RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ -TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s -b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 -WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS -fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB -zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq -hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB -CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD -+JbNR6iC8hZVdyR+EhCVBCyj ------END CERTIFICATE----- - # Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI # Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI # Label: "emSign Root CA - C1" @@ -2725,96 +2224,6 @@ PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE QRBdJ3NghVdJIgc= -----END CERTIFICATE----- -# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global Certification Authority" -# Serial: 1846098327275375458322922162 -# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e -# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 -# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 ------BEGIN CERTIFICATE----- -MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x -ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 -c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx -OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI -SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI -b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn -swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu -7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 -1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW -80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP -JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l -RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw -hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 -coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc -BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n -twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud -DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W -0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe -uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q -lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB -aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE -sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT -MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe -qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh -VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 -h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 -EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK -yeC2nOnOcXHebD8WpHk= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P256 Certification Authority" -# Serial: 4151900041497450638097112925 -# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 -# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf -# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 ------BEGIN CERTIFICATE----- -MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG -SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN -FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w -DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw -CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh -DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P384 Certification Authority" -# Serial: 2704997926503831671788816187 -# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 -# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 -# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 ------BEGIN CERTIFICATE----- -MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB -BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ -j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF -1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G -A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 -AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC -MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu -Sw== ------END CERTIFICATE----- - # Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. # Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. # Label: "NAVER Global Root Certification Authority" @@ -3375,45 +2784,6 @@ Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c -----END CERTIFICATE----- -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 159662449406622349769042896298 -# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc -# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 -# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt -nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY -6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu -MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k -RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg -f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV -+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo -dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW -Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa -G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq -gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H -vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 -0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC -B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u -NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg -yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev -HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 -xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR -TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg -JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV -7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl -6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL ------END CERTIFICATE----- - # Issuer: CN=GTS Root R3 O=Google Trust Services LLC # Subject: CN=GTS Root R3 O=Google Trust Services LLC # Label: "GTS Root R3" @@ -4066,30 +3436,6 @@ ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= -----END CERTIFICATE----- -# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" -# Serial: 65916896770016886708751106294915943533 -# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 -# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 -# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a ------BEGIN CERTIFICATE----- -MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf -e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C -cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O -BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw -hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG -XSaQpYXFuXqUPoeovQA= ------END CERTIFICATE----- - # Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA # Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA # Label: "TWCA CYBER Root CA" @@ -4130,35 +3476,6 @@ xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X -----END CERTIFICATE----- -# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Label: "SecureSign Root CA12" -# Serial: 587887345431707215246142177076162061960426065942 -# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 -# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 -# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u -LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw -NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD -eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS -b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF -KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt -p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd -J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur -FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J -hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K -h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF -AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld -mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ -mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA -8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV -55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ -yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== ------END CERTIFICATE----- - # Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. # Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. # Label: "SecureSign Root CA14" @@ -4429,7 +3746,7 @@ YmH5LVerVrkR3SW+ak5KGoJr3M/TvEqzPNcum9v4KGm8ay3sMaE641c= # Issuer: CN=OISTE Server Root RSA G1 O=OISTE Foundation # Subject: CN=OISTE Server Root RSA G1 O=OISTE Foundation -# Label: " OISTE Server Root RSA G1" +# Label: "OISTE Server Root RSA G1" # Serial: 113845518112613905024960613408179309848 # MD5 Fingerprint: 23:a7:9e:d4:70:b8:b9:14:57:41:8a:7e:44:59:e2:68 # SHA1 Fingerprint: f7:00:34:25:94:88:68:31:e4:34:87:3f:70:fe:86:b3:86:9f:f0:6e @@ -4466,3 +3783,154 @@ J8tRd/iWkx7P8nd9H0aTolkelUTFLXVksNb54Dxp6gS1HAviRkRNQzuXSXERvSS2 wq1yVAb+axj5d9spLFKebXd7Yv0PTY6YMjAwcRLWJTXjn/hvnLXrahut6hDTlhZy BiElxky8j3C7DOReIoMt0r7+hVu05L0= -----END CERTIFICATE----- + +# Issuer: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Subject: CN=e-Szigno TLS Root CA 2023 O=Microsec Ltd. +# Label: "e-Szigno TLS Root CA 2023" +# Serial: 71934828665710877219916191754 +# MD5 Fingerprint: 6a:e9:99:74:a5:da:5e:f1:d9:2e:f2:c8:d1:86:8b:71 +# SHA1 Fingerprint: 6f:9a:d5:d5:df:e8:2c:eb:be:37:07:ee:4f:4f:52:58:29:41:d1:fe +# SHA256 Fingerprint: b4:91:41:50:2d:00:66:3d:74:0f:2e:7e:c3:40:c5:28:00:96:26:66:12:1a:36:d0:9c:f7:dd:2b:90:38:4f:b4 +-----BEGIN CERTIFICATE----- +MIICzzCCAjGgAwIBAgINAOhvGHvWOWuYSkmYCjAKBggqhkjOPQQDBDB1MQswCQYD +VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 +ZC4xFzAVBgNVBGEMDlZBVEhVLTIzNTg0NDk3MSIwIAYDVQQDDBllLVN6aWdubyBU +TFMgUm9vdCBDQSAyMDIzMB4XDTIzMDcxNzE0MDAwMFoXDTM4MDcxNzE0MDAwMFow +dTELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRYwFAYDVQQKDA1NaWNy +b3NlYyBMdGQuMRcwFQYDVQRhDA5WQVRIVS0yMzU4NDQ5NzEiMCAGA1UEAwwZZS1T +emlnbm8gVExTIFJvb3QgQ0EgMjAyMzCBmzAQBgcqhkjOPQIBBgUrgQQAIwOBhgAE +AGgP36J8PKp0iGEKjcJMpQEiFNT3YHdCnAo4YKGMZz6zY+n6kbCLS+Y53wLCMAFS +AL/fjO1ZrTJlqwlZULUZwmgcAOAFX9pQJhzDrAQixTpN7+lXWDajwRlTEArRzT/v +SzUaQ49CE0y5LBqcvjC2xN7cS53kpDzLLtmt3999Cd8ukv+ho2MwYTAPBgNVHRMB +Af8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUWYQCYlpGePVd3I8K +ECgj3NXW+0UwHwYDVR0jBBgwFoAUWYQCYlpGePVd3I8KECgj3NXW+0UwCgYIKoZI +zj0EAwQDgYsAMIGHAkIBLdqu9S54tma4n7Zwf2Z0z+yOfP7AAXmazlIC58PRDHpt +y7Ve7hekm9sEdu4pKeiv+62sUvTXK9Z3hBC9xdIoaDQCQTV2WnXzkoYI9bIeCvZl +C9p2x1L/Cx6AcCIwwzPbGO2E14vs7dOoY4G1VnxHx1YwlGhza9IuqbnZLBwpvQy6 +uWWL +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS RSA Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS RSA Root CA 2024" +# Serial: 17188327524208271538 +# MD5 Fingerprint: d0:a4:db:32:eb:44:98:d2:62:0b:3e:bc:4d:7c:5c:e9 +# SHA1 Fingerprint: fb:97:96:7c:ef:8d:98:63:06:c0:3b:b6:11:f8:e0:13:97:a2:98:d3 +# SHA256 Fingerprint: 14:35:f2:25:c5:d2:52:d7:a2:19:48:cc:3c:e6:2a:ec:fa:88:00:1e:3d:d7:2d:1c:c3:55:51:00:eb:37:2f:93 +-----BEGIN CERTIFICATE----- +MIIFmjCCA4KgAwIBAgIJAO6JNNDLgOCyMA0GCSqGSIb3DQEBDAUAMFoxCzAJBgNV +BAYTAkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEj +MCEGA1UEAxMaU0VDT00gVExTIFJTQSBSb290IENBIDIwMjQwHhcNMjQwMTMxMDUx +MTU1WhcNNDkwMTE0MDUxMTU1WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VD +T00gVHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBS +U0EgUm9vdCBDQSAyMDI0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4TjizUwzxbInq8Tx11gaFYNk5fO+34y7TyM4neh0UgL5JIZbJNLTz2x//L/B71+5 +m6X6nGIr7d4lFJBGtjO677hXOz93zkcWaUTm3VbOAjBlt4YWxlcccBHXuZ7o3Q+4 +R+ormrBdHeJ1CTUEG8ttQbKIl3G7OZYbnH8/pP8cjPub/0kDVNuMzp7xsVRROOis +Qt53fMoJLlYgoebbuMphOqMCtjkJ7R6efEMfLp8UAVi9ZaLRn76ET/CJkk925ndu +uufC4BatS4mnXFmxN0vUXb0ij9B8O/D8gixQEsVSD4GK8FWRPh3bVd/6bzdkHGJj +y21XI0yejVomZUbRrOfNuz0boPGV1pt18fFC39IHQEth3OFqb5NDO3L+A9bNqTgA +yUgRmIn4ucgDc/Ri/Km3V51ueZjy1/yk0qwJVadAVVrCt56iNeXOyEvzJADGgDQ8 +E1Pdaqct8Cynz/47ReQM62vFYO08wcQkrjmX/tesiko1V1yyaf6EfPzUFzmaGy9x +vkCwdbm15EdTolOjE0H2Vb5/APDOyCFEokiYGmXTLdAUl0wKZ4IyjkHGzy0jhpaX +EXE/GJcEvI6VzEchjaBL03EJ0h9pG4OqeIOycKvAo3A+TbetyfsrgYyHzU0a7/qU +jGat1AAq1nVljMpKqpinPTsf/d9H39FTUeJL7TpzzjUCAwEAAaNjMGEwHQYDVR0O +BBYEFCzrchKOWHdkNRVWNQFXB6l9DTbmMB8GA1UdIwQYMBaAFCzrchKOWHdkNRVW +NQFXB6l9DTbmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqG +SIb3DQEBDAUAA4ICAQAVwsvluSafaez5tFPR/hRTBzRxEyMMQF3XJXCVi3yegZyK +oec7hmE6jx2ZM8KgM1kn2yJRwFXHX8zUW9nBLEDWc4wuE8LrlZqhGZM9pJQXGmGz +ResDJV6JgRBna+j4sA1M7yIdlvL0sAfFXFCTRaWTD4E1V99RLrFzWfTcC+e180hD +uNMpqOEo46+lMeW/Wvh7ifOQs+kiK0O2gHxQDNxslSavnCs4V7l8HRDJ2La10o70 +Bo7VLzf1W8MBvv0VTnxB+NjT5qTAbhGFh9Gvp4BaJpmdUf0C5CEP6dbQlfgxWfzY +r69yVT6dPQB+GFEaY03IMY+AcBCs+om1fNxrQXt9zoofMBNFbLhvpNH/JsXWdGUz +fNbO12uswTa5wah8LB18FTQN2/zPHYmvBEoLuyUgZ09VNLJo5YA0kXItVYkLjMe2 +SixzK4scUHv81IK99I91DWx7FwMVKw2xgFp+ZLYB2dnpQQrqwlW64glHUcK2N9BD +snjLSxeZ+UPECh9RxH4WAcKiZW+cqaKMmhP2WBfR4IcR7NOL32ml11ds87hhV1CZ +WWFCJAcCidYZz6CZa8exzHojP9SB5RH0/v1KdHAisqhSjtJl/UIAHIQ48elOn8wr +TdFap4Yb5aHglmMeNx+fAIhDluWVfxTO7H4dTPU+SFVRMLAh+wwKZfqb94nMeQ== +-----END CERTIFICATE----- + +# Issuer: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Subject: CN=SECOM TLS ECC Root CA 2024 O=SECOM Trust Systems Co., Ltd. +# Label: "SECOM TLS ECC Root CA 2024" +# Serial: 9329818985461676612 +# MD5 Fingerprint: 99:d3:9d:e4:d2:b1:2d:f0:2a:04:67:85:f3:df:46:d6 +# SHA1 Fingerprint: 7a:1f:22:2d:72:b2:c3:19:87:44:db:61:69:e8:a6:4b:d7:0d:44:0e +# SHA256 Fingerprint: 6a:b2:ab:75:f5:1c:b4:f4:f0:15:62:03:fb:f6:f6:46:23:2f:51:4b:e0:59:f6:28:33:30:8b:82:b4:d7:2d:b1 +-----BEGIN CERTIFICATE----- +MIICTDCCAdGgAwIBAgIJAIF6LO+PI3pEMAoGCCqGSM49BAMDMFoxCzAJBgNVBAYT +AkpQMSYwJAYDVQQKEx1TRUNPTSBUcnVzdCBTeXN0ZW1zIENvLiwgTHRkLjEjMCEG +A1UEAxMaU0VDT00gVExTIEVDQyBSb290IENBIDIwMjQwHhcNMjQwMTMxMDU1MjM0 +WhcNNDkwMTE0MDU1MjM0WjBaMQswCQYDVQQGEwJKUDEmMCQGA1UEChMdU0VDT00g +VHJ1c3QgU3lzdGVtcyBDby4sIEx0ZC4xIzAhBgNVBAMTGlNFQ09NIFRMUyBFQ0Mg +Um9vdCBDQSAyMDI0MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE7NzFMtu9dzQXSNC1 +2fabk0+GlC5finB3R7XaZonRUd20aFiWObtuNBCLUZSfk6QXAE55BjEXsXQ/NG8y +UqicXjsu9ksDK3JZBgCwLOVh6+nwJXTvso/dEj/GUYH5mBdoo2MwYTAdBgNVHQ4E +FgQUO3YReyl04k4GTFaCQNAhL3qzydUwHwYDVR0jBBgwFoAUO3YReyl04k4GTFaC +QNAhL3qzydUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI +zj0EAwMDaQAwZgIxAN3ib8fi1pMYtAPjMilB5e5/H+t5CL0xPL+cZ5oTTZuSCjpA +n1v7F/VAr8bFxQXAowIxAKsBVO1ACFp7skwzPvdv1EUY5a897WGLT4lb+bjxFAWy +l8wDcZJdwGZ/pAHxt1AJ1g== +-----END CERTIFICATE----- + +# Issuer: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia EC TLS Root CA v3 O=Telia Company AB +# Label: "Telia EC TLS Root CA v3" +# Serial: 8028200332079443110287896228123317 +# MD5 Fingerprint: b6:fa:6a:5c:42:fb:c5:67:72:c0:e0:2f:72:fb:5a:44 +# SHA1 Fingerprint: b4:d6:07:c2:a5:95:bc:5b:f4:67:4d:c9:dc:6f:6f:0a:00:7a:a5:35 +# SHA256 Fingerprint: 09:8e:08:a9:1d:bb:f7:74:78:b9:6c:ce:b8:9b:14:13:a5:da:37:b7:c8:62:60:6a:95:5d:eb:07:17:9f:43:26 +-----BEGIN CERTIFICATE----- +MIICMjCCAbegAwIBAgIPAYvSIlRjTQSLbOVHH9K1MAoGCCqGSM49BAMDMEoxCzAJ +BgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSAwHgYDVQQDDBdU +ZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzAeFw0yMzExMTUwODU1MjZaFw00ODA1MjMx +MTAwMDBaMEoxCzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFC +MSAwHgYDVQQDDBdUZWxpYSBFQyBUTFMgUm9vdCBDQSB2MzB2MBAGByqGSM49AgEG +BSuBBAAiA2IABMHIlhVDLbmFKUpW0iK4dpryT6emYOeS31JPwWnWPmkWRrAkTbPX +40sQfHI9mpR7Rbktu3ngg6W+BBSXSechtMCnBmWXj/EaVlmV5cY1jD2HoTfhBQ3A +acpCNMLJK4NpZaNjMGEwHwYDVR0jBBgwFoAU1GToQ4g6cy/QGnGCNgtehd7H3kMw +HQYDVR0OBBYEFNRk6EOIOnMv0BpxgjYLXoXex95DMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MAoGCCqGSM49BAMDA2kAMGYCMQCXAUdS/9bbJ8A1JYaG +f/bWt/s7Ta0ot5Ulno8OjSNYRWQIlS4tVWldvTAVA7heOFgCMQCvKr8+Z2Rn+OBr +5UHzlgBObpad1LuwNTRcdNgUJxIWadcki+UBLEi1/AURKV5md2M= +-----END CERTIFICATE----- + +# Issuer: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Subject: CN=Telia RSA TLS Root CA v3 O=Telia Company AB +# Label: "Telia RSA TLS Root CA v3" +# Serial: 8028214673410753541795188766939845 +# MD5 Fingerprint: f4:8b:9c:f3:f8:63:cf:dc:25:8f:b4:bb:a2:e9:9d:e2 +# SHA1 Fingerprint: b5:2e:88:4e:40:c1:11:fb:50:c7:e2:4f:ac:18:2b:bd:68:15:d2:34 +# SHA256 Fingerprint: d1:3d:b1:29:4c:45:eb:c6:fc:86:c6:bb:f6:9f:a2:9b:df:e6:92:df:f7:c7:13:c2:43:c7:a9:56:c6:a2:28:4c +-----BEGIN CERTIFICATE----- +MIIFgjCCA2qgAwIBAgIPAYvSUKtCVSxHWr2h3BrFMA0GCSqGSIb3DQEBDAUAMEsx +CzAJBgNVBAYTAlNFMRkwFwYDVQQKDBBUZWxpYSBDb21wYW55IEFCMSEwHwYDVQQD +DBhUZWxpYSBSU0EgVExTIFJvb3QgQ0EgdjMwHhcNMjMxMTE1MDk0NzQyWhcNNDgw +NTIzMTEwMDAwWjBLMQswCQYDVQQGEwJTRTEZMBcGA1UECgwQVGVsaWEgQ29tcGFu +eSBBQjEhMB8GA1UEAwwYVGVsaWEgUlNBIFRMUyBSb290IENBIHYzMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsV89KG19hCf4S1Fvk8D3TyDERhmcvx8F +7Kmb4WATx3ije1id3KHxRE0TKmcNCbAQ57bvHFEYa4hR2l20VjVadExqOW+2ld99 +MbEiO+jRVOz+BbxLxJnmGwCqI+BfuTjjVReDxsxjQvjgBsClaO/sm5i70nlZcWGR +tIkvWDK3NNkT5RtwXc/O8NTFVpbUqT6cRjIj3olAblR+lRf4Ffy5o+Q9fabjYn9Z +9S4itruElcEFf9Ljk7fwdTycT/rvJW9w/B3G2a3r0f/zXNOVruIBcqE6pkSospAC +U2bG42fYKrbM/GWnp7u+p9Frz4jaNwpb4YHuEeS8BratNcP8X62jXIvvKHxlsMDJ +Cnb4U8JzFOLsU6mohVY58BdZrvi0Gk9UOuqmgoG6dskHoksjZTlK61D/InzmEoA1 +yAYJFDVysjRxDUOu9cAwANbqmq77WIFL6BpnZgVqPtMfG6wN8BrTKdapvilVsYR5 +9BFgIsAVBMxrGh+W+QcvmJafUpASvlArKvVG2FI4i6PiLjSBT0+6F6EQLrYqefOQ +F/fBNEXb+njUQ0SUVrAqtH4Y+OjCI/a4/JJQppxeemZcQ0SUShgiI5AM5xHO5iya +UrTjYH4zxUz9j+1FEbDH/xpstr1gXBykspup+hRTaJcbA+UbpJqtWZndAPddJmt6 +YJQ+dU3pDu8CAwEAAaNjMGEwHwYDVR0jBBgwFoAUsMep0t2yKFZzBJSMFFxIbzdS +kqgwHQYDVR0OBBYEFLDHqdLdsihWcwSUjBRcSG83UpKoMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQBdYzFsNGDRk7bR +/AgRKq+5637YuOW+w6uhpoS0VnKMUpyHCwku86hEvqivakPtfmlm4bFwt++sb/8O +XsWBqtfbXMaBNDTZl8XRMJuLWOW2JrbKkRzgG0eBUcvsadG1rrhbmZqYvFXaAZO7 +o4TdOZzxhBB5GOAWWXB3IeraNP4J63zyo9n8Gqw3sJBG44em5hoYjBffP+npibys +lnslRi4L6xHsCYj/Pab+OlqbMCB6v+sTCLeEIukRVzoR9aQ45pEK7Z1QBnSsbAKQ +tss0JKD9d/mX143H1xePjPhTXlv5JCkhrcj+SShz0P9+EHoWe6m9lyUEOIVn0rp+ +yVJWNbmyDv3VkwFxHC1ApSQsgSimjGQ4wtr6cSmordYxkV+Ro8lOIIhRksXPyDk2 +7gW6IjUXCkZKpxFjkL3jiBSc8SkxnwCWtXg8xwNwdFVNBGLCCuJnsneYXjJNqzRq +UcoGwzsvF3Qi/ZnHUNvISdevlgIAXL4Wvrxaqvoa01wB+GCfs57RTGE4TvAGhKNK +us8K3hRT1BSpigzMIRzSxtAOrqPN6j//QSmW9f8Jcncri4j2ihSpVrFU0NdNkMhZ +eAKidTFPsxCVFuW4Aniz7jqiw5sWtjbQrlW035izIEU4sYwQoC1Nx0Svy+mMTRai +50LqFQ+A1/Hq6xHHDNx7CI83d23Erw== +-----END CERTIFICATE----- diff --git a/src/pip/_vendor/vendor.txt b/src/pip/_vendor/vendor.txt index dd2f5f3485..677e52a931 100644 --- a/src/pip/_vendor/vendor.txt +++ b/src/pip/_vendor/vendor.txt @@ -6,7 +6,7 @@ packaging==26.0 platformdirs==4.5.1 pyproject-hooks==1.2.0 requests==2.32.5 - certifi==2026.1.4 + certifi==2026.10.19 idna==3.11 urllib3==1.26.20 rich==14.2.0 diff --git a/tests/unit/test_base_command.py b/tests/unit/test_base_command.py index d3e1a75272..23abf8b0b6 100644 --- a/tests/unit/test_base_command.py +++ b/tests/unit/test_base_command.py @@ -100,14 +100,14 @@ def test_raise_broken_stdout__debug_logging( assert "Traceback (most recent call last):" in stderr -@patch("pip._internal.cli.index_command.Command.handle_pip_version_check") -def test_handle_pip_version_check_called(mock_handle_version_check: Mock) -> None: +@patch("pip._internal.cli.index_command.Command.pip_version_check") +def test_pip_version_check_called(mock_version_check: Mock) -> None: """ - Check that Command.handle_pip_version_check() is called. + Check that ``Command.pip_version_check()`` wraps the command body. """ cmd = FakeCommand() cmd.main([]) - mock_handle_version_check.assert_called_once() + mock_version_check.assert_called_once() def test_debug_enables_verbose_logs() -> None: diff --git a/tests/unit/test_commands.py b/tests/unit/test_commands.py index 04ddd0316c..471027bd33 100644 --- a/tests/unit/test_commands.py +++ b/tests/unit/test_commands.py @@ -95,16 +95,16 @@ def has_option_no_index(command: Command) -> bool: @pytest.mark.parametrize( "disable_pip_version_check, no_index, expected_called", [ - # pip_self_version_check() is only called when both - # disable_pip_version_check and no_index are False. + # The fetch phase only runs when both disable_pip_version_check + # and no_index are False. (False, False, True), (False, True, False), (True, False, False), (True, True, False), ], ) -@mock.patch("pip._internal.cli.index_command._pip_self_version_check") -def test_index_group_handle_pip_version_check( +@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") +def test_index_group_pip_version_check( mock_version_check: mock.Mock, command_name: str, disable_pip_version_check: bool, @@ -112,26 +112,52 @@ def test_index_group_handle_pip_version_check( expected_called: bool, ) -> None: """ - Test whether pip_self_version_check() is called when - handle_pip_version_check() is called, for each of the - IndexGroupCommand classes. + Test whether the pre-body fetch runs when ``pip_version_check()`` is + entered, for each of the IndexGroupCommand classes. """ command = create_command(command_name) options = command.parser.get_default_values() options.disable_pip_version_check = disable_pip_version_check options.no_index = no_index + # Return None so the emit branch is a no-op. + mock_version_check.return_value = None # See test test_list_pip_version_check() below. if command_name == "list": expected_called = False - command.handle_pip_version_check(options) + with command.pip_version_check(options, []): + pass if expected_called: mock_version_check.assert_called_once() else: mock_version_check.assert_not_called() +@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") +def test_install_pip_version_check_skipped_when_pip_is_a_requirement( + mock_version_check: mock.Mock, +) -> None: + """``pip install pip`` must skip the self-version check: the running pip + may be replaced before emit.""" + command = create_command("install") + options = command.parser.get_default_values() + options.disable_pip_version_check = False + options.no_index = False + + with command.pip_version_check(options, ["pip"]): + pass + mock_version_check.assert_not_called() + + with command.pip_version_check(options, ["pip==25.0"]): + pass + mock_version_check.assert_not_called() + + with command.pip_version_check(options, ["some-other-pkg"]): + pass + mock_version_check.assert_called_once() + + def test_requirement_commands() -> None: """ Test which commands inherit from RequirementCommand. @@ -144,7 +170,7 @@ def is_requirement_command(command: Command) -> bool: @pytest.mark.parametrize("flag", ["", "--outdated", "--uptodate"]) -@mock.patch("pip._internal.cli.index_command._pip_self_version_check") +@mock.patch("pip._internal.cli.index_command._pip_self_version_check_fetch") @mock.patch.dict(os.environ, {"PIP_DISABLE_PIP_VERSION_CHECK": "no"}) def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> None: """ @@ -158,3 +184,25 @@ def test_list_pip_version_check(version_check_mock: mock.Mock, flag: str) -> Non version_check_mock.assert_called_once() else: version_check_mock.assert_not_called() + + +@pytest.mark.parametrize( + "exc", + [ValueError("Invalid IPv6 URL")], +) +def test_arg_refers_to_pip_tolerates_value_error(exc: Exception) -> None: + """A malformed requirement must not crash the pre-command version check.""" + from pip._internal.commands import install + + with mock.patch.object(install, "Requirement", side_effect=exc): + assert install._arg_refers_to_pip("foo @ http://[bad/x") is False + + +@pytest.mark.parametrize( + "arg, expected", + [("pip", True), ("pip==26.0.1", True), ("Pip", True), ("six", False)], +) +def test_arg_refers_to_pip(arg: str, expected: bool) -> None: + from pip._internal.commands import install + + assert install._arg_refers_to_pip(arg) is expected diff --git a/tests/unit/test_link.py b/tests/unit/test_link.py index c49f8547ac..e494e95d84 100644 --- a/tests/unit/test_link.py +++ b/tests/unit/test_link.py @@ -1,9 +1,22 @@ from __future__ import annotations +import os +import posixpath + import pytest -from pip._internal.exceptions import InvalidEggFragment, PipError -from pip._internal.models.link import Link, links_equivalent +from pip._internal.exceptions import ( + InstallationError, + InvalidEggFragment, + PipError, + UnsafeFilenameError, +) +from pip._internal.models.link import ( + Link, + as_path_component, + join_within_directory, + links_equivalent, +) from pip._internal.utils.hashes import Hashes @@ -29,6 +42,13 @@ def test_repr(self, url: str, expected: str) -> None: ("https://example.com/path/page.html", "page.html"), # Test a quoted character. ("https://example.com/path/page%231.html", "page#1.html"), + # A doubly-encoded separator must stay encoded: the path is decoded + # exactly once, so the file name keeps its literal "%2F" instead of + # collapsing into a "/". + ( + "https://example.com/a%252Fb.whl", + "a%2Fb.whl", + ), ( "http://yo/myproject-1.0%2Bfoobar.0-py2.py3-none-any.whl", "myproject-1.0+foobar.0-py2.py3-none-any.whl", @@ -49,6 +69,52 @@ def test_filename(self, url: str, expected: str) -> None: link = Link(url) assert link.filename == expected + @pytest.mark.parametrize( + "url", + [ + "https://example.com/a%252Fb.whl", + "https://example.com/%252e%252e%252fb.whl", + ], + ) + def test_filename_decoded_once_stays_single_component(self, url: str) -> None: + # The path is decoded exactly once, so an encoded separator stays + # encoded and the file name remains a single path component rather + # than collapsing into a "/"-separated path. + filename = Link(url).filename + assert not posixpath.isabs(filename) + assert posixpath.basename(filename) == filename + + @pytest.mark.parametrize( + "url", + [ + "https://example.com/..", + "https://example.com/.", + "https://example.com/foo/%2e%2e", + ], + ) + def test_filename_parent_reference_falls_back_to_netloc(self, url: str) -> None: + # A path that is only a "." or ".." reference has no usable file name, + # so filename falls back to the netloc rather than handing back a + # traversal component that could escape a download directory. + assert Link(url).filename == "example.com" + + @pytest.mark.parametrize( + "url", + [ + # A path-less URL whose authority looks like a traversal: the netloc + # fallback must still reduce to a single path component. + "http://..\\..\\..\\evil.whl", + "http://../", + "http://..", + ], + ) + def test_filename_is_always_a_path_component(self, url: str) -> None: + # filename must never carry a separator or parent reference, so joining + # it onto a directory can never escape that directory. + name = Link(url).filename + assert os.path.basename(name) == name + assert name not in (os.curdir, os.pardir) + def test_splitext(self) -> None: assert ("wheel", ".whl") == Link("http://yo/wheel.whl").splitext() @@ -244,3 +310,62 @@ def test_links_equivalent(url1: str, url2: str) -> None: ) def test_links_equivalent_false(url1: str, url2: str) -> None: assert not links_equivalent(Link(url1), Link(url2)) + + +@pytest.mark.parametrize( + "name", + [ + "wheel.whl", + "myproject-1.0+foobar.0-py2.py3-none-any.whl", + # A literal "%2F" is a normal file name, not a separator. + "a%2Fb.whl", + ], +) +def test_as_path_component_keeps_plain_name(name: str) -> None: + assert as_path_component(name) == name + + +@pytest.mark.parametrize( + "name", + [ + os.path.join(os.sep, "abs", "pkg.whl"), + os.path.join("..", "pkg.whl"), + os.path.join("nested", "pkg.whl"), + ], +) +def test_as_path_component_reduces_to_basename(name: str) -> None: + # A name carrying directory components is reduced to its basename, so the + # result always stays inside the directory it is later joined onto. + assert as_path_component(name) == os.path.basename(name) + + +@pytest.mark.parametrize("name", ["", ".", "..", "/", os.path.join("sub", "..")]) +def test_as_path_component_rejects_empty_or_parent_reference(name: str) -> None: + with pytest.raises(ValueError): + as_path_component(name) + + +def test_as_path_component_error_is_installation_error() -> None: + # Still a ValueError for existing callers, but pip's command loop reports + # an InstallationError as a plain "ERROR:" line rather than a traceback. + with pytest.raises(UnsafeFilenameError) as exc_info: + as_path_component("..") + assert isinstance(exc_info.value, InstallationError) + assert isinstance(exc_info.value, ValueError) + + +@pytest.mark.parametrize( + "name", + [ + "pkg.whl", + # A literal "%2F" is a normal file name, not a separator. + "a%2Fb.whl", + ], +) +def test_join_within_directory_stays_inside(name: str) -> None: + # The component is joined onto the directory as its final element, so the + # result stays inside the directory. + directory = os.path.join("base", "downloads") + joined = join_within_directory(directory, as_path_component(name)) + assert joined == os.path.join(directory, name) + assert os.path.basename(joined) == name diff --git a/tests/unit/test_self_check_outdated.py b/tests/unit/test_self_check_outdated.py index 4b3c36f682..4e6b68e1ad 100644 --- a/tests/unit/test_self_check_outdated.py +++ b/tests/unit/test_self_check_outdated.py @@ -7,7 +7,7 @@ import sys from optparse import Values from pathlib import Path -from unittest.mock import ANY, Mock, patch +from unittest.mock import Mock, patch import pytest from freezegun import freeze_time @@ -15,10 +15,22 @@ from pip._vendor.packaging.version import Version from pip._internal import self_outdated_check -from pip._internal.self_outdated_check import UpgradePrompt, pip_self_version_check +from pip._internal.self_outdated_check import ( + UpgradePrompt, + pip_self_version_check_emit, + pip_self_version_check_fetch, +) from pip._internal.utils.misc import ExternallyManagedEnvironment +def _make_installed_dist(version: str, installer: str = "pip") -> Mock: + """Build a stand-in for the installed pip distribution.""" + installed_dist = Mock() + installed_dist.version = Version(version) + installed_dist.installer = installer + return installed_dist + + @pytest.mark.parametrize( "key, expected", [ @@ -37,32 +49,60 @@ def test_get_statefile_name_known_values(key: str, expected: str) -> None: @freeze_time("1970-01-02T11:00:00Z") -@patch("pip._internal.self_outdated_check._self_version_check_logic") +@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") @patch("pip._internal.self_outdated_check.SelfCheckState") +@patch("pip._internal.self_outdated_check.get_default_environment") @patch("pip._internal.self_outdated_check.check_externally_managed", new=lambda: None) -def test_pip_self_version_check_calls_underlying_implementation( - mocked_state: Mock, mocked_function: Mock, tmpdir: Path +def test_pip_self_version_check_fetch_calls_underlying_implementation( + mocked_env: Mock, mocked_state: Mock, mocked_get_remote: Mock, tmpdir: Path ) -> None: # GIVEN mock_session = Mock() fake_options = Values({"cache_dir": str(tmpdir)}) - mocked_function.return_value = None + mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") + mocked_state.return_value.get.return_value = None + mocked_get_remote.return_value = "5.0" # WHEN - self_outdated_check.pip_self_version_check(mock_session, fake_options) + result = pip_self_version_check_fetch(mock_session, fake_options) # THEN + assert result == UpgradePrompt(old="1.0", new="5.0") mocked_state.assert_called_once_with(cache_dir=str(tmpdir)) - mocked_function.assert_called_once_with( - state=mocked_state(cache_dir=str(tmpdir)), - current_time=datetime.datetime( - 1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc - ), - local_version=ANY, - get_remote_version=ANY, + mocked_get_remote.assert_called_once_with(mock_session, fake_options) + mocked_state.return_value.set.assert_called_once_with( + "5.0", + datetime.datetime(1970, 1, 2, 11, 0, 0, tzinfo=datetime.timezone.utc), ) +def test_pip_self_version_check_emit_logs_prompt( + caplog: pytest.LogCaptureFixture, +) -> None: + # GIVEN + prompt = UpgradePrompt(old="1.0", new="2.0") + + # WHEN + with caplog.at_level(logging.WARNING): + pip_self_version_check_emit(prompt) + + # THEN + assert len(caplog.records) == 1 + assert caplog.records[0].levelno == logging.WARNING + + +def test_pip_self_version_check_emit_no_prompt_is_silent( + caplog: pytest.LogCaptureFixture, +) -> None: + # WHEN + with caplog.at_level(logging.WARNING): + pip_self_version_check_emit(None) + + # THEN + assert caplog.records == [] + + +@freeze_time("2000-01-01T00:00:00Z") @pytest.mark.parametrize( [ # noqa: PT006 - String representation is too long "installed_version", @@ -94,27 +134,41 @@ def test_core_logic( should_show_prompt: bool, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch, + tmpdir: Path, ) -> None: # GIVEN + installed_dist = _make_installed_dist( + installed_version, installer="pip" if installed_by_pip else "apt" + ) + monkeypatch.setattr( + self_outdated_check, + "get_default_environment", + lambda: Mock(get_distribution=Mock(return_value=installed_dist)), + ) + monkeypatch.setattr(self_outdated_check, "check_externally_managed", lambda: None) monkeypatch.setattr( - self_outdated_check, "was_installed_by_pip", lambda _: installed_by_pip + self_outdated_check, + "_get_current_remote_pip_version", + lambda session, options: remote_version, ) - mock_state = Mock() - mock_state.get.return_value = stored_version - fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0) + mock_state_instance = Mock() + mock_state_instance.get.return_value = stored_version + monkeypatch.setattr( + self_outdated_check, + "SelfCheckState", + Mock(return_value=mock_state_instance), + ) + fake_time = datetime.datetime(2000, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) version_that_should_be_checked = stored_version or remote_version # WHEN with caplog.at_level(logging.DEBUG): - return_value = self_outdated_check._self_version_check_logic( - state=mock_state, - current_time=fake_time, - local_version=Version(installed_version), - get_remote_version=lambda: remote_version, + return_value = pip_self_version_check_fetch( + session=Mock(), options=Values({"cache_dir": str(tmpdir)}) ) # THEN - mock_state.get.assert_called_once_with(fake_time) + mock_state_instance.get.assert_called_once_with(fake_time) assert caplog.messages == [ f"Remote version of pip: {version_that_should_be_checked}", f"Local version of pip: {installed_version}", @@ -122,9 +176,9 @@ def test_core_logic( ] if stored_version: - mock_state.set.assert_not_called() + mock_state_instance.set.assert_not_called() else: - mock_state.set.assert_called_once_with( + mock_state_instance.set.assert_called_once_with( version_that_should_be_checked, fake_time ) @@ -196,13 +250,29 @@ def test_writes_expected_statefile(self, tmpdir: Path) -> None: assert statefile_permissions == selfcheckdir_permissions == cache_permissions -@patch("pip._internal.self_outdated_check._self_version_check_logic") -def test_suppressed_by_externally_managed(mocked_function: Mock, tmpdir: Path) -> None: - mocked_function.return_value = UpgradePrompt(old="1.0", new="2.0") +@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") +@patch("pip._internal.self_outdated_check.get_default_environment") +def test_fetch_suppressed_by_externally_managed( + mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path +) -> None: + mocked_env.return_value.get_distribution.return_value = _make_installed_dist("1.0") fake_options = Values({"cache_dir": str(tmpdir)}) with patch( "pip._internal.self_outdated_check.check_externally_managed", side_effect=ExternallyManagedEnvironment("nope"), ): - pip_self_version_check(session=Mock(), options=fake_options) - mocked_function.assert_not_called() + result = pip_self_version_check_fetch(session=Mock(), options=fake_options) + assert result is None + mocked_get_remote.assert_not_called() + + +@patch("pip._internal.self_outdated_check._get_current_remote_pip_version") +@patch("pip._internal.self_outdated_check.get_default_environment") +def test_fetch_skipped_when_pip_not_installed( + mocked_env: Mock, mocked_get_remote: Mock, tmpdir: Path +) -> None: + mocked_env.return_value.get_distribution.return_value = None + fake_options = Values({"cache_dir": str(tmpdir)}) + result = pip_self_version_check_fetch(session=Mock(), options=fake_options) + assert result is None + mocked_get_remote.assert_not_called() diff --git a/tests/unit/test_utils_unpacking.py b/tests/unit/test_utils_unpacking.py index 582da9722b..69e1bfe962 100644 --- a/tests/unit/test_utils_unpacking.py +++ b/tests/unit/test_utils_unpacking.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import io import os import shutil @@ -8,12 +10,19 @@ import time import zipfile from pathlib import Path +from unittest.mock import MagicMock, patch import pytest from _pytest.monkeypatch import MonkeyPatch from pip._internal.exceptions import InstallationError -from pip._internal.utils.unpacking import is_within_directory, untar_file, unzip_file +from pip._internal.utils.unpacking import ( + _tar_member_paths, + is_within_directory, + unpack_file, + untar_file, + unzip_file, +) from tests.lib import TestData @@ -381,6 +390,165 @@ def test_unpack_evil_tar_link2_no_data_filter( assert not os.path.exists(os.path.join(extract_path, "evil_symlink")) + def test_unpack_tar_symlink_then_member_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """Reject a symlink to outside before a member is written through it.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + + tar_filepath = os.path.join(self.tempdir, "symlink_then_member.tar") + extract_path = os.path.join(self.tempdir, "extract_path") + outside_path = os.path.join(self.tempdir, "outside.txt") + + with tarfile.open(tar_filepath, "w") as tar: + info = tarfile.TarInfo("outside_link") + info.type = tarfile.SYMTYPE + info.linkname = ".." + tar.addfile(info) + + data = io.BytesIO(b"data\n") + info = tarfile.TarInfo("outside_link/outside.txt") + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + + info = tarfile.TarInfo("..") + info.type = tarfile.DIRTYPE + tar.addfile(info) + + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + + assert not os.path.exists(outside_path) + assert not os.path.exists(os.path.join(extract_path, "outside_link")) + + def test_unpack_tar_nested_symlink_traversal_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """Reject a member that escapes through a chain of in-bounds symlinks.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + + tar_filepath = os.path.join(self.tempdir, "nested_traversal.tar") + extract_path = os.path.join(self.tempdir, "extract_path") + outside_path = os.path.join(self.tempdir, "outside.txt") + + with tarfile.open(tar_filepath, "w") as tar: + info = tarfile.TarInfo(".") + info.type = tarfile.DIRTYPE + tar.addfile(info) + + info = tarfile.TarInfo("redir") + info.type = tarfile.SYMTYPE + info.linkname = "." + tar.addfile(info) + + info = tarfile.TarInfo("redir/up") + info.type = tarfile.SYMTYPE + info.linkname = ".." + tar.addfile(info) + + data = io.BytesIO(b"data\n") + info = tarfile.TarInfo("redir/up/outside.txt") + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + + assert not os.path.exists(outside_path) + + def _make_tar(self, name: str, entries: list[tuple[str, str, str]]) -> str: + tar_filepath = os.path.join(self.tempdir, name) + with tarfile.open(tar_filepath, "w") as tar: + for member_name, kind, arg in entries: + info = tarfile.TarInfo(member_name) + if kind == "dir": + info.type = tarfile.DIRTYPE + tar.addfile(info) + elif kind == "sym": + info.type = tarfile.SYMTYPE + info.linkname = arg + tar.addfile(info) + else: + data = io.BytesIO(arg.encode()) + info.size = len(data.getbuffer()) + tar.addfile(info, fileobj=data) + return tar_filepath + + def test_unpack_tar_dot_prefixed_symlink_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A "./"-prefixed archive (tar -C dir .) with an internal symlink.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "dot_prefixed.tar", + [ + ("./", "dir", ""), + ("./pkg", "dir", ""), + ("./pkg/target.txt", "file", "data\n"), + ("./pkg/link", "sym", "target.txt"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + untar_file(tar_filepath, extract_path) + link = os.path.join(extract_path, "pkg", "link") + assert os.path.islink(link) + assert os.readlink(link) == "target.txt" + + def test_unpack_tar_symlink_to_implicit_dir_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A symlink to a directory that has no directory entry of its own.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "implicit_dir.tar", + [ + # A second top-level member stops untar_file stripping "pkg/". + ("README", "file", "data\n"), + ("pkg/sub/f.txt", "file", "data\n"), + ("pkg/link", "sym", "sub"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + untar_file(tar_filepath, extract_path) + assert os.path.islink(os.path.join(extract_path, "pkg", "link")) + + def test_unpack_tar_symlink_to_non_member_no_data_filter( + self, monkeypatch: MonkeyPatch + ) -> None: + """A symlink to a path that is not in the archive is still refused.""" + if hasattr(tarfile, "data_filter"): + monkeypatch.delattr("tarfile.data_filter") + tar_filepath = self._make_tar( + "non_member.tar", + [ + ("pkg/f.txt", "file", "data\n"), + ("pkg/link", "sym", "missing.txt"), + ], + ) + extract_path = os.path.join(self.tempdir, "extract_path") + with pytest.raises(InstallationError): + untar_file(tar_filepath, extract_path) + assert not os.path.lexists(os.path.join(extract_path, "pkg", "link")) + + +def test_tar_member_paths_terminates_on_doubled_slash() -> None: + """posixpath.dirname("//") == "//"; collecting parents must not loop.""" + buf = io.BytesIO() + with tarfile.open(fileobj=buf, mode="w") as tar: + data = io.BytesIO(b"x") + info = tarfile.TarInfo("//abs/f.txt") + info.size = 1 + tar.addfile(info, fileobj=data) + buf.seek(0) + with tarfile.open(fileobj=buf) as tar: + paths = _tar_member_paths(tar) + assert "//abs/f.txt" in paths + assert "//abs" in paths + def test_unpack_tar_unicode(tmpdir: Path) -> None: test_tar = tmpdir / "test.tar" @@ -419,3 +587,229 @@ def test_unpack_tar_unicode(tmpdir: Path) -> None: def test_is_within_directory(args: tuple[str, str], expected: bool) -> None: result = is_within_directory(*args) assert result == expected + + +@pytest.mark.skipif(sys.platform == "win32", reason="POSIX path semantics") +@pytest.mark.parametrize( + "directory, target, expected", + [ + # os.path.commonpath drops the POSIX "//" prefix; abspath keeps it. + ("//srv/env/bin", "//srv/env/bin/pip", True), + ("//srv/env/bin", "//srv/env/binfoo/pip", False), + # A filesystem root already ends with a separator. + ("/", "/usr/bin/pip", True), + ("/", "/", True), + ], +) +def test_is_within_directory_posix_roots( + directory: str, target: str, expected: bool +) -> None: + assert is_within_directory(directory, target) is expected + + +@pytest.mark.skipif(sys.platform != "win32", reason="drive letters only on Windows") +def test_is_within_directory_other_drive() -> None: + # A target on another drive is outside, and the check must not raise. + assert is_within_directory("C:\\env", "D:\\outside") is False + assert is_within_directory("C:\\", "C:\\env\\pip") is True + + +@pytest.mark.parametrize( + "is_zip, is_tar, unzip, untar, exception", + [ + # zip file + (True, False, True, False, False), + # tar file + (False, True, False, True, False), + # neither zip nor tar + (False, False, False, False, True), + # ambiguous (both zip and tar) + (True, True, False, False, True), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_magic_signature_check_logic( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + is_zip: bool, + is_tar: bool, + unzip: bool, + untar: bool, + exception: bool, +) -> None: + """ + Test that pip throws an error if file is identified as both zip and tar + and all other checks came out undeterministic. + """ + mock_tarfile.is_tarfile.return_value = is_tar + mock_zipfile.is_zipfile.return_value = is_zip + filename = "ambiguous-file.unknown-extension" + + if exception: + with pytest.raises(InstallationError): + unpack_file(filename, "any-location", content_type=None) + else: + unpack_file(filename, "any-location", content_type=None) + + mock_unzip.assert_called_once() if unzip else mock_unzip.assert_not_called() + mock_untar.assert_called_once() if untar else mock_untar.assert_not_called() + mock_tarfile.is_tarfile.assert_called_once() + mock_zipfile.is_zipfile.assert_called_once() + + +@pytest.mark.parametrize( + "filename, content_type, unzip, untar", + [ + # content_type check + ("noname", "application/zip", True, False), + ("noname", "application/x-gzip", False, True), + # filename check + ("ok.zip", None, True, False), + ("ok.tar.gz", None, False, True), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_check_priority( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + filename: str, + content_type: str | None, + unzip: bool, + untar: bool, +) -> None: + """ + Test the order of priority of checks to ensure + we don't use magic signature check unless we have to. + """ + unpack_file(filename, "any-location", content_type=content_type) + mock_unzip.assert_called_once() if unzip else mock_unzip.assert_not_called() + mock_untar.assert_called_once() if untar else mock_untar.assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize( + "filename, expect_unzip", + [ + ("pkg.zip", True), + ("pkg.ZIP", True), + ("pkg-1.0-py3-none-any.whl", True), + ("pkg.tar.gz", False), + ("pkg.TAR.GZ", False), + ("pkg.tgz", False), + ("pkg.tar", False), + ("pkg.tar.bz2", False), + ("pkg.tbz", False), + ("pkg.tar.xz", False), + ("pkg.txz", False), + ("pkg.tlz", False), + ("pkg.tar.lz", False), + ("pkg.tar.lzma", False), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_filename_extension_routing( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + filename: str, + expect_unzip: bool, +) -> None: + unpack_file(filename, "any-location", content_type=None) + (mock_unzip if expect_unzip else mock_untar).assert_called_once() + (mock_untar if expect_unzip else mock_unzip).assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize( + "content_type, filename, expect_unzip", + [ + ("application/zip", "pkg.tar.gz", True), + ("application/x-gzip", "pkg.zip", False), + ("application/x-gzip", "pkg.whl", False), + ("application/octet-stream", "pkg.zip", True), + ("application/octet-stream", "pkg.tar.gz", False), + ], +) +@patch("pip._internal.utils.unpacking.tarfile") +@patch("pip._internal.utils.unpacking.zipfile") +@patch("pip._internal.utils.unpacking.untar_file") +@patch("pip._internal.utils.unpacking.unzip_file") +def test_content_type_vs_filename_priority( + mock_unzip: MagicMock, + mock_untar: MagicMock, + mock_zipfile: MagicMock, + mock_tarfile: MagicMock, + content_type: str, + filename: str, + expect_unzip: bool, +) -> None: + unpack_file(filename, "any-location", content_type=content_type) + (mock_unzip if expect_unzip else mock_untar).assert_called_once() + (mock_untar if expect_unzip else mock_unzip).assert_not_called() + mock_zipfile.is_zipfile.assert_not_called() + mock_tarfile.is_tarfile.assert_not_called() + + +@pytest.mark.parametrize("filename, flatten", [("pkg.whl", False), ("pkg.zip", True)]) +@patch("pip._internal.utils.unpacking.unzip_file") +def test_flatten_only_for_non_whl( + mock_unzip: MagicMock, filename: str, flatten: bool +) -> None: + unpack_file(filename, "any-location", content_type=None) + assert mock_unzip.call_args.kwargs["flatten"] is flatten + + +def _write_polyglot(path: Path) -> None: + """Write a tar.gz with a zip appended; both views contain payload.txt.""" + tar_buf = io.BytesIO() + with tarfile.open(fileobj=tar_buf, mode="w:gz") as tar: + info = tarfile.TarInfo("pkg/payload.txt") + info.size = 8 + tar.addfile(info, io.BytesIO(b"from-tar")) + zip_buf = io.BytesIO() + with zipfile.ZipFile(zip_buf, "w") as zf: + zf.writestr("pkg/payload.txt", "from-zip") + path.write_bytes(tar_buf.getvalue() + zip_buf.getvalue()) + + +@pytest.mark.parametrize( + "filename, content_type, expected", + [ + ("pkg.tar.gz", None, b"from-tar"), + ("pkg.tgz", None, b"from-tar"), + ("pkg.zip", None, b"from-zip"), + ("pkg.tar.gz", "application/zip", b"from-zip"), + ("pkg.unknown", "application/x-gzip", b"from-tar"), + ], +) +def test_polyglot_routing( + tmp_path: Path, filename: str, content_type: str | None, expected: bytes +) -> None: + archive = tmp_path / filename + _write_polyglot(archive) + out = tmp_path / "out" + unpack_file(str(archive), str(out), content_type=content_type) + assert (out / "payload.txt").read_bytes() == expected + + +def test_polyglot_ambiguous_name_rejected(tmp_path: Path) -> None: + archive = tmp_path / "pkg.bin" + _write_polyglot(archive) + with pytest.raises(InstallationError): + unpack_file(str(archive), str(tmp_path / "out")) diff --git a/tests/unit/test_wheel.py b/tests/unit/test_wheel.py index e0ac6497b7..2f9bba70ab 100644 --- a/tests/unit/test_wheel.py +++ b/tests/unit/test_wheel.py @@ -462,6 +462,32 @@ def test_invalid_entrypoints_fail( assert os.path.basename(wheel_path) in exc_text assert entrypoint in exc_text + @pytest.mark.parametrize("bad_name", ["../../outside", "..", "."]) + @pytest.mark.parametrize("entry_point_type", ["console_scripts", "gui_scripts"]) + def test_wheel_install_rejects_entry_point_path_traversal( + self, data: TestData, tmpdir: Path, bad_name: str, entry_point_type: str + ) -> None: + """An entry point name with separators or ``..`` must not install a + script outside the scripts directory. + """ + self.prep(data, tmpdir) + wheel_path = make_wheel( + "simple", + "0.1.0", + entry_points={entry_point_type: [f"{bad_name} = simple:main"]}, + ).save_to_dir(tmpdir) + with pytest.raises(InstallationError) as e: + wheel.install_wheel( + "simple", + str(wheel_path), + scheme=self.scheme, + req_description="simple", + ) + + assert "outside the scripts directory" in str(e.value) + # Nothing was written outside the install destination. + assert not os.path.exists(os.path.join(str(tmpdir), "outside")) + class TestMessageAboutScriptsNotOnPATH: tilde_warning_msg = ( @@ -665,3 +691,50 @@ def test_get_console_script_specs_replaces_python_version( "not_pip_or_easy_install-99 = whatever", "not_pip_or_easy_install-99.88 = whatever", ] + + +@pytest.mark.parametrize( + "name, within", + [ + ("pip", True), + ("pip3.13", True), + ("foo-bar.baz", True), + ("...", True), # a literal filename, not a path component + ("sub/script", True), # in-tree subdirectory + ("a/../b", True), + ("sub\\script", True), # backslash stays in-tree on POSIX and Windows + (" ../../inside", True), # distlib keeps a leading space; resolves in-tree + ("../outside", False), + ("../../outside", False), + ("a/../../outside", False), + ("/etc/cron.d/outside", False), # absolute path; os.path.join drops the root + # "." and ".." pass PyPI's [\w.-]+ name check but must be rejected here. + (".", False), + ("..", False), + ("", False), + ], +) +def test_script_within_dir(name: str, within: bool) -> None: + assert wheel._script_within_dir(name, "/srv/env/bin") is within + + +def test_script_within_dir_allows_doubled_slash_root() -> None: + # A scripts directory can have a doubled leading slash + assert wheel._script_within_dir("pip", "//srv/env/bin") is True + assert wheel._script_within_dir("../outside", "//srv/env/bin") is False + + +@pytest.mark.skipif(WINDOWS, reason="POSIX filesystem root") +def test_script_within_dir_filesystem_root() -> None: + # A scripts directory at the filesystem root accepts ordinary scripts... + assert wheel._script_within_dir("pip", "/") is True + # ...but never the directory itself. + assert wheel._script_within_dir(".", "/") is False + assert wheel._script_within_dir("", "/") is False + + +@pytest.mark.skipif(not WINDOWS, reason="drive letters only matter on Windows") +def test_script_within_dir_rejects_other_drive() -> None: + # Validate that a script on a different drive is rejected, + # and doesn't throw an error + assert wheel._script_within_dir("D:\\outside", "C:\\env\\bin") is False