hashes.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  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. @property
  38. def digest_count(self):
  39. # type: () -> int
  40. return sum(len(digests) for digests in self._allowed.values())
  41. def is_hash_allowed(
  42. self,
  43. hash_name, # type: str
  44. hex_digest, # type: str
  45. ):
  46. # type: (...) -> bool
  47. """Return whether the given hex digest is allowed."""
  48. return hex_digest in self._allowed.get(hash_name, [])
  49. def check_against_chunks(self, chunks):
  50. # type: (Iterator[bytes]) -> None
  51. """Check good hashes against ones built from iterable of chunks of
  52. data.
  53. Raise HashMismatch if none match.
  54. """
  55. gots = {}
  56. for hash_name in iterkeys(self._allowed):
  57. try:
  58. gots[hash_name] = hashlib.new(hash_name)
  59. except (ValueError, TypeError):
  60. raise InstallationError(
  61. 'Unknown hash name: {}'.format(hash_name)
  62. )
  63. for chunk in chunks:
  64. for hash in itervalues(gots):
  65. hash.update(chunk)
  66. for hash_name, got in iteritems(gots):
  67. if got.hexdigest() in self._allowed[hash_name]:
  68. return
  69. self._raise(gots)
  70. def _raise(self, gots):
  71. # type: (Dict[str, _Hash]) -> NoReturn
  72. raise HashMismatch(self._allowed, gots)
  73. def check_against_file(self, file):
  74. # type: (BinaryIO) -> None
  75. """Check good hashes against a file-like object
  76. Raise HashMismatch if none match.
  77. """
  78. return self.check_against_chunks(read_chunks(file))
  79. def check_against_path(self, path):
  80. # type: (str) -> None
  81. with open(path, 'rb') as file:
  82. return self.check_against_file(file)
  83. def __nonzero__(self):
  84. # type: () -> bool
  85. """Return whether I know any known-good hashes."""
  86. return bool(self._allowed)
  87. def __bool__(self):
  88. # type: () -> bool
  89. return self.__nonzero__()
  90. class MissingHashes(Hashes):
  91. """A workalike for Hashes used when we're missing a hash for a requirement
  92. It computes the actual hash of the requirement and raises a HashMissing
  93. exception showing it to the user.
  94. """
  95. def __init__(self):
  96. # type: () -> None
  97. """Don't offer the ``hashes`` kwarg."""
  98. # Pass our favorite hash in to generate a "gotten hash". With the
  99. # empty list, it will never match, so an error will always raise.
  100. super(MissingHashes, self).__init__(hashes={FAVORITE_HASH: []})
  101. def _raise(self, gots):
  102. # type: (Dict[str, _Hash]) -> NoReturn
  103. raise HashMissing(gots[FAVORITE_HASH].hexdigest())