From: Brian Warner Date: Sun, 23 Mar 2008 21:46:49 +0000 (-0700) Subject: download: make plaintext and ciphertext hashes in the UEB optional. X-Git-Tag: allmydata-tahoe-1.0.0~19 X-Git-Url: https://git.rkrishnan.org/listings/frontends/FTP-and-SFTP.rst?a=commitdiff_plain;h=553367d56763f3442ff28a5c5b7ea62865ae4ff0;p=tahoe-lafs%2Ftahoe-lafs.git download: make plaintext and ciphertext hashes in the UEB optional. Removing the plaintext hashes can help with the guess-partial-information attack. This does not affect compatibility, but if and when we actually remove any hashes from the share, that will introduce a forwards-compatibility break: tahoe-0.9 will not be able to read such files. --- diff --git a/src/allmydata/download.py b/src/allmydata/download.py index 66c399c5..2485ec10 100644 --- a/src/allmydata/download.py +++ b/src/allmydata/download.py @@ -664,11 +664,13 @@ class FileDownloader: self._tail_codec = codec.get_decoder_by_name(d['codec_name']) self._tail_codec.set_serialized_params(d['tail_codec_params']) - crypttext_hash = d['crypttext_hash'] - assert isinstance(crypttext_hash, str) - assert len(crypttext_hash) == 32 + crypttext_hash = d.get('crypttext_hash', None) # optional + if crypttext_hash: + assert isinstance(crypttext_hash, str) + assert len(crypttext_hash) == 32 self._crypttext_hash = crypttext_hash - self._plaintext_hash = d['plaintext_hash'] + self._plaintext_hash = d.get('plaintext_hash', None) # optional + self._roothash = d['share_root_hash'] self._segment_size = segment_size = d['segment_size'] @@ -682,12 +684,18 @@ class FileDownloader: self._get_hashtrees_started = time.time() if self._status: self._status.set_status("Retrieving Hash Trees") - d = self._get_plaintext_hashtrees() + d = defer.maybeDeferred(self._get_plaintext_hashtrees) d.addCallback(self._get_crypttext_hashtrees) d.addCallback(self._setup_hashtrees) return d def _get_plaintext_hashtrees(self): + # plaintext hashes are optional. If the root isn't in the UEB, then + # the share will be holding an empty list. We don't even bother + # fetching it. + if "plaintext_root_hash" not in self._uri_extension_data: + self._plaintext_hashtree = None + return def _validate_plaintext_hashtree(proposal, bucket): if proposal[0] != self._uri_extension_data['plaintext_root_hash']: self._fetch_failures["plaintext_hashroot"] += 1 @@ -713,6 +721,10 @@ class FileDownloader: return d def _get_crypttext_hashtrees(self, res): + # crypttext hashes are optional too + if "crypttext_root_hash" not in self._uri_extension_data: + self._crypttext_hashtree = None + return def _validate_crypttext_hashtree(proposal, bucket): if proposal[0] != self._uri_extension_data['crypttext_root_hash']: self._fetch_failures["crypttext_hashroot"] += 1 @@ -915,12 +927,12 @@ class FileDownloader: self._results.timings["total"] = now - self._started self._results.timings["segments"] = now - self._started_fetching self._output.close() - if self.check_crypttext_hash: + if self.check_crypttext_hash and self._crypttext_hash: _assert(self._crypttext_hash == self._output.crypttext_hash, "bad crypttext_hash: computed=%s, expected=%s" % (base32.b2a(self._output.crypttext_hash), base32.b2a(self._crypttext_hash))) - if self.check_plaintext_hash: + if self.check_plaintext_hash and self._plaintext_hash: _assert(self._plaintext_hash == self._output.plaintext_hash, "bad plaintext_hash: computed=%s, expected=%s" % (base32.b2a(self._output.plaintext_hash), diff --git a/src/allmydata/test/test_encode.py b/src/allmydata/test/test_encode.py index cfd3bbf7..b8ad8961 100644 --- a/src/allmydata/test/test_encode.py +++ b/src/allmydata/test/test_encode.py @@ -25,8 +25,8 @@ class FakeBucketWriterProxy: def __init__(self, mode="good"): self.mode = mode self.blocks = {} - self.plaintext_hashes = None - self.crypttext_hashes = None + self.plaintext_hashes = [] + self.crypttext_hashes = [] self.block_hashes = None self.share_hashes = None self.closed = False @@ -57,14 +57,14 @@ class FakeBucketWriterProxy: def put_plaintext_hashes(self, hashes): def _try(): assert not self.closed - assert self.plaintext_hashes is None + assert not self.plaintext_hashes self.plaintext_hashes = hashes return defer.maybeDeferred(_try) def put_crypttext_hashes(self, hashes): def _try(): assert not self.closed - assert self.crypttext_hashes is None + assert not self.crypttext_hashes self.crypttext_hashes = hashes return defer.maybeDeferred(_try)