hashes.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from __future__ import absolute_import
  2. import hashlib
  3. from pip._vendor.six import iteritems, iterkeys, itervalues
  4. from pip._internal.exceptions import (
  5. HashMismatch,
  6. HashMissing,
  7. InstallationError,
  8. )
  9. from pip._internal.utils.misc import read_chunks
  10. from pip._internal.utils.typing import MYPY_CHECK_RUNNING
  11. if MYPY_CHECK_RUNNING:
  12. from typing import (
  13. Dict, List, BinaryIO, NoReturn, Iterator
  14. )
  15. from pip._vendor.six import PY3
  16. if PY3:
  17. from hashlib import _Hash
  18. else:
  19. from hashlib import _hash as _Hash
  20. # The recommended hash algo of the moment. Change this whenever the state of
  21. # the art changes; it won't hurt backward compatibility.
  22. FAVORITE_HASH = 'sha256'
  23. # Names of hashlib algorithms allowed by the --hash option and ``pip hash``
  24. # Currently, those are the ones at least as collision-resistant as sha256.
  25. STRONG_HASHES = ['sha256', 'sha384', 'sha512']
  26. class Hashes(object):
  27. """A wrapper that builds multiple hashes at once and checks them against
  28. known-good values
  29. """
  30. def __init__(self, hashes=None):
  31. # type: (Dict[str, List[str]]) -> None
  32. """
  33. :param hashes: A dict of algorithm names pointing to lists of allowed
  34. hex digests
  35. """
  36. self._allowed = {} if hashes is None else hashes
  37. def __or__(self, other):
  38. # type: (Hashes) -> Hashes
  39. if not isinstance(other, Hashes):
  40. return NotImplemented
  41. new = self._allowed.copy()
  42. for alg, values in iteritems(other._allowed):
  43. try:
  44. new[alg] += values
  45. except KeyError:
  46. new[alg] = values
  47. return Hashes(new)
  48. @property
  49. def digest_count(self):
  50. # type: () -> int
  51. return sum(len(digests) for digests in self._allowed.values())
  52. def is_hash_allowed(
  53. self,
  54. hash_name, # type: str
  55. hex_digest, # type: str
  56. ):
  57. # type: (...) -> bool
  58. """Return whether the given hex digest is allowed."""
  59. return hex_digest in self._allowed.get(hash_name, [])
  60. def check_against_chunks(self, chunks):
  61. # type: (Iterator[bytes]) -> None
  62. """Check good hashes against ones built from iterable of chunks of
  63. data.
  64. Raise HashMismatch if none match.
  65. """
  66. gots = {}
  67. for hash_name in iterkeys(self._allowed):
  68. try:
  69. gots[hash_name] = hashlib.new(hash_name)
  70. except (ValueError, TypeError):
  71. raise InstallationError(
  72. 'Unknown hash name: {}'.format(hash_name)
  73. )
  74. for chunk in chunks:
  75. for hash in itervalues(gots):
  76. hash.update(chunk)
  77. for hash_name, got in iteritems(gots):
  78. if got.hexdigest() in self._allowed[hash_name]:
  79. return
  80. self._raise(gots)
  81. def _raise(self, gots):
  82. # type: (Dict[str, _Hash]) -> NoReturn
  83. raise HashMismatch(self._allowed, gots)
  84. def check_against_file(self, file):
  85. # type: (BinaryIO) -> None
  86. """Check good hashes against a file-like object
  87. Raise HashMismatch if none match.
  88. """
  89. return self.check_against_chunks(read_chunks(file))
  90. def check_against_path(self, path):
  91. # type: (str) -> None
  92. with open(path, 'rb') as file:
  93. return self.check_against_file(file)
  94. def __nonzero__(self):
  95. # type: () -> bool
  96. """Return whether I know any known-good hashes."""
  97. return bool(self._allowed)
  98. def __bool__(self):
  99. # type: () -> bool
  100. return self.__nonzero__()
  101. class MissingHashes(Hashes):
  102. """A workalike for Hashes used when we're missing a hash for a requirement
  103. It computes the actual hash of the requirement and raises a HashMissing
  104. exception showing it to the user.
  105. """
  106. def __init__(self):
  107. # type: () -> None
  108. """Don't offer the ``hashes`` kwarg."""
  109. # Pass our favorite hash in to generate a "gotten hash". With the
  110. # empty list, it will never match, so an error will always raise.
  111. super(MissingHashes, self).__init__(hashes={FAVORITE_HASH: []})
  112. def _raise(self, gots):
  113. # type: (Dict[str, _Hash]) -> NoReturn
  114. raise HashMissing(gots[FAVORITE_HASH].hexdigest())