def __init__(self, file, mode="r", compression=ZIP_DEFLATED): basename = os.path.basename(file) self.parsed_filename = WHEEL_INFO_RE.match(basename) if not basename.endswith(".whl") or self.parsed_filename is None: raise WheelError(f"Bad wheel filename {basename!r}") ZipFile.__init__(self, file, mode, compression=compression, allowZip64=True) self.dist_info_path = "{}.dist-info".format( self.parsed_filename.group("namever")) self.record_path = self.dist_info_path + "/RECORD" self._file_hashes = OrderedDict() self._file_sizes = {} if mode == "r": # Ignore RECORD and any embedded wheel signatures self._file_hashes[self.record_path] = None, None self._file_hashes[self.record_path + ".jws"] = None, None self._file_hashes[self.record_path + ".p7s"] = None, None # Fill in the expected hashes by reading them from RECORD try: record = self.open(self.record_path) except KeyError: raise WheelError(f"Missing {self.record_path} file") with record: for line in csv.reader( TextIOWrapper(record, newline="", encoding="utf-8")): path, hash_sum, size = line if not hash_sum: continue algorithm, hash_sum = hash_sum.split("=") try: hashlib.new(algorithm) except ValueError: raise WheelError( f"Unsupported hash algorithm: {algorithm}") if algorithm.lower() in {"md5", "sha1"}: raise WheelError( "Weak hash algorithm ({}) is not permitted by PEP " "427".format(algorithm)) self._file_hashes[path] = ( algorithm, urlsafe_b64decode(hash_sum.encode("ascii")), )
def __init__(self, file, mode='r'): basename = os.path.basename(file) self.parsed_filename = WHEEL_INFO_RE.match(basename) if not basename.endswith('.whl') or self.parsed_filename is None: raise WheelError("Bad wheel filename {!r}".format(basename)) ZipFile.__init__(self, file, mode, compression=ZIP_DEFLATED, allowZip64=True) self.dist_info_path = '{}.dist-info'.format( self.parsed_filename.group('namever')) self.record_path = self.dist_info_path + '/RECORD' self._file_hashes = OrderedDict() self._file_sizes = {} if mode == 'r': # Ignore RECORD and any embedded wheel signatures self._file_hashes[self.record_path] = None, None self._file_hashes[self.record_path + '.jws'] = None, None self._file_hashes[self.record_path + '.p7s'] = None, None # Fill in the expected hashes by reading them from RECORD try: record = self.open(self.record_path) except KeyError: raise WheelError('Missing {} file'.format(self.record_path)) with record: for line in record: line = line.decode('utf-8') path, hash_sum, size = line.rsplit(u',', 2) if hash_sum: algorithm, hash_sum = hash_sum.split(u'=') try: hashlib.new(algorithm) except ValueError: raise WheelError( 'Unsupported hash algorithm: {}'.format( algorithm)) if algorithm.lower() in {'md5', 'sha1'}: raise WheelError( 'Weak hash algorithm ({}) is not permitted by PEP 427' .format(algorithm)) self._file_hashes[path] = ( algorithm, urlsafe_b64decode(hash_sum.encode('ascii')))
def verify(self, zipfile=None): """Configure the VerifyingZipFile `zipfile` by verifying its signature and setting expected hashes for every hash in RECORD. Caller must complete the verification process by completely reading every file in the archive (e.g. with extractall).""" sig = None if zipfile is None: zipfile = self.zipfile zipfile.strict = True record_name = '/'.join((self.distinfo_name, 'RECORD')) sig_name = '/'.join((self.distinfo_name, 'RECORD.jws')) # tolerate s/mime signatures: smime_sig_name = '/'.join((self.distinfo_name, 'RECORD.p7s')) zipfile.set_expected_hash(record_name, None) zipfile.set_expected_hash(sig_name, None) zipfile.set_expected_hash(smime_sig_name, None) record = zipfile.read(record_name) record_digest = urlsafe_b64encode(hashlib.sha256(record).digest()) try: sig = from_json(native(zipfile.read(sig_name))) except KeyError: # no signature pass if sig: headers, payload = signatures.verify(sig) if payload['hash'] != "sha256=" + native(record_digest): msg = "RECORD.sig claimed RECORD hash {0} != computed hash {1}." raise BadWheelFile( msg.format(payload['hash'], native(record_digest))) reader = csv.reader((native(r) for r in record.splitlines())) for row in reader: filename = row[0] hash = row[1] if not hash: if filename not in (record_name, sig_name): sys.stderr.write("%s has no hash!\n" % filename) continue algo, data = row[1].split('=', 1) assert algo == "sha256", "Unsupported hash algorithm" zipfile.set_expected_hash(filename, urlsafe_b64decode(binary(data)))
def verify(self, zipfile=None): """Configure the VerifyingZipFile `zipfile` by verifying its signature and setting expected hashes for every hash in RECORD. Caller must complete the verification process by completely reading every file in the archive (e.g. with extractall).""" sig = None if zipfile is None: zipfile = self.zipfile zipfile.strict = True record_name = '/'.join((self.distinfo_name, 'RECORD')) sig_name = '/'.join((self.distinfo_name, 'RECORD.jws')) # tolerate s/mime signatures: smime_sig_name = '/'.join((self.distinfo_name, 'RECORD.p7s')) zipfile.set_expected_hash(record_name, None) zipfile.set_expected_hash(sig_name, None) zipfile.set_expected_hash(smime_sig_name, None) record = zipfile.read(record_name) record_digest = urlsafe_b64encode(hashlib.sha256(record).digest()) try: sig = from_json(native(zipfile.read(sig_name))) except KeyError: # no signature pass if sig: headers, payload = signatures.verify(sig) if payload['hash'] != "sha256=" + native(record_digest): msg = "RECORD.sig claimed RECORD hash {0} != computed hash {1}." raise BadWheelFile(msg.format(payload['hash'], native(record_digest))) reader = csv.reader((native(r) for r in record.splitlines())) for row in reader: filename = row[0] hash = row[1] if not hash: if filename not in (record_name, sig_name): sys.stderr.write("%s has no hash!\n" % filename) continue algo, data = row[1].split('=', 1) assert algo == "sha256", "Unsupported hash algorithm" zipfile.set_expected_hash(filename, urlsafe_b64decode(binary(data)))
def __init__(self, file, mode='r'): basename = os.path.basename(file) self.parsed_filename = WHEEL_INFO_RE.match(basename) if not basename.endswith('.whl') or self.parsed_filename is None: raise WheelError("Bad wheel filename {!r}".format(basename)) super(WheelFile, self).__init__(file, mode, compression=ZIP_DEFLATED, allowZip64=True) self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever')) self.record_path = self.dist_info_path + '/RECORD' self._file_hashes = OrderedDict() self._file_sizes = {} if mode == 'r': # Ignore RECORD and any embedded wheel signatures self._file_hashes[self.record_path] = None, None self._file_hashes[self.record_path + '.jws'] = None, None self._file_hashes[self.record_path + '.p7s'] = None, None # Fill in the expected hashes by reading them from RECORD try: record = self.open(self.record_path) except KeyError: raise WheelError('Missing {} file'.format(self.record_path)) with record: for line in record: line = line.decode('utf-8') path, hash_sum, size = line.rsplit(u',', 2) if hash_sum: algorithm, hash_sum = hash_sum.split(u'=') try: hashlib.new(algorithm) except ValueError: raise WheelError('Unsupported hash algorithm: {}'.format(algorithm)) if algorithm.lower() in {'md5', 'sha1'}: raise WheelError( 'Weak hash algorithm ({}) is not permitted by PEP 427' .format(algorithm)) self._file_hashes[path] = ( algorithm, urlsafe_b64decode(hash_sum.encode('ascii')))