Skip to content

Commit cb35544

Browse files
committed
Reject variable-length (SHAKE) checksum algorithms instead of crashing
bagit advertised the SHAKE algorithms (shake_128, shake_256) as usable manifest checksums because they are in hashlib.algorithms_guaranteed, but their hexdigest() needs a length argument that bagit never supplies, so make_bag crashed with a TypeError partway through. Detect variable-length digests by behavior (not by name) and filter them out of CHECKSUM_ALGOS, raise a clear BagError if one is requested directly, and skip them in get_hashers as a safeguard. Closes #158. Signed-off-by: Arpit Jain <arpitjain099@gmail.com>
1 parent 4bd2713 commit cb35544

2 files changed

Lines changed: 69 additions & 2 deletions

File tree

src/bagit/__init__.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,38 @@ def find_locale_dir():
120120
# Payload-Oxum is autogenerated
121121
]
122122

123+
124+
def _has_fixed_length_digest(alg):
125+
"""
126+
Return True if the named algorithm produces a fixed-length digest.
127+
128+
The variable-length algorithms (the SHAKE family) require a ``length``
129+
argument to ``hexdigest()`` that bagit never supplies, so they cannot be
130+
used for manifests. We detect that by behavior rather than by name so that
131+
any future variable-length algorithm hashlib adds is handled too.
132+
"""
133+
134+
try:
135+
hashlib.new(alg).hexdigest()
136+
except TypeError:
137+
# Variable-length digest, e.g. shake_128/shake_256
138+
return False
139+
except ValueError:
140+
# Unsupported algorithm; leave it for the existing warning path
141+
return True
142+
return True
143+
144+
123145
try:
124-
CHECKSUM_ALGOS = hashlib.algorithms_guaranteed
146+
CHECKSUM_ALGOS = {
147+
alg for alg in hashlib.algorithms_guaranteed if _has_fixed_length_digest(alg)
148+
}
125149
except AttributeError:
126150
# FIXME: remove when we drop Python 2 (https://github.com/LibraryOfCongress/bagit-python/issues/102)
127151
# Python 2.7.0-2.7.8
128-
CHECKSUM_ALGOS = set(hashlib.algorithms)
152+
CHECKSUM_ALGOS = {
153+
alg for alg in hashlib.algorithms if _has_fixed_length_digest(alg)
154+
}
129155
DEFAULT_CHECKSUMS = ["sha256", "sha512"]
130156

131157
#: Block size used when reading files for hashing:
@@ -160,6 +186,13 @@ def make_bag(
160186
if checksums is None:
161187
checksums = DEFAULT_CHECKSUMS
162188

189+
variable_length = [alg for alg in checksums if not _has_fixed_length_digest(alg)]
190+
if variable_length:
191+
raise BagError(
192+
_("Cannot create manifests with variable-length digest algorithm(s): %s")
193+
% ", ".join(sorted(variable_length))
194+
)
195+
163196
bag_dir = os.path.abspath(bag_dir)
164197
cwd = os.path.abspath(os.path.curdir)
165198

@@ -1129,6 +1162,15 @@ def get_hashers(algorithms):
11291162
)
11301163
continue
11311164

1165+
if not _has_fixed_length_digest(alg):
1166+
LOGGER.warning(
1167+
_(
1168+
"Disabling requested hash algorithm %s: it has a variable-length digest"
1169+
),
1170+
alg,
1171+
)
1172+
continue
1173+
11321174
hashers[alg] = hasher
11331175

11341176
if not hashers:

test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,31 @@ def test_make_bag_md5_sha1_sha256_manifest(self):
9696
# check valid with three manifests
9797
self.assertTrue(self.validate(bag, fast=True))
9898

99+
def test_make_bag_shake_raises_bagerror(self):
100+
# SHAKE algorithms have a variable-length digest and cannot be used for
101+
# manifests; make_bag should reject them cleanly instead of crashing
102+
# with a TypeError later on. See issue #158.
103+
for alg in ("shake_128", "shake_256"):
104+
self.assertRaises(
105+
bagit.BagError, bagit.make_bag, self.tmpdir, checksums=[alg]
106+
)
107+
108+
def test_make_bag_shake_mixed_with_valid_raises_bagerror(self):
109+
# A SHAKE algorithm mixed in with a usable one should still be rejected
110+
# rather than crashing in the tagmanifest path.
111+
self.assertRaises(
112+
bagit.BagError,
113+
bagit.make_bag,
114+
self.tmpdir,
115+
checksums=["sha256", "shake_128"],
116+
)
117+
118+
def test_shake_not_offered_as_checksum_algorithm(self):
119+
# Variable-length algorithms must not be advertised, so they are never
120+
# auto-discovered as a manifest type or exposed as a CLI flag.
121+
self.assertNotIn("shake_128", bagit.CHECKSUM_ALGOS)
122+
self.assertNotIn("shake_256", bagit.CHECKSUM_ALGOS)
123+
99124
def test_validate_flipped_bit(self):
100125
bag = bagit.make_bag(self.tmpdir)
101126
readme = j(self.tmpdir, "data", "README")

0 commit comments

Comments
 (0)