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.
This commit is contained in:
Brian Warner 2008-03-23 14:46:49 -07:00
parent 7acb115c6e
commit 553367d567
2 changed files with 23 additions and 11 deletions

View File

@ -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),

View File

@ -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)