hashes.py 3.9 KB

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