]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/commitdiff
use added secret to protect convergent encryption
authorZooko O'Whielacronx <zooko@zooko.com>
Mon, 24 Mar 2008 16:46:06 +0000 (09:46 -0700)
committerZooko O'Whielacronx <zooko@zooko.com>
Mon, 24 Mar 2008 16:46:06 +0000 (09:46 -0700)
Now upload or encode methods take a required argument named "convergence" which can be either None, indicating no convergent encryption at all, or a string, which is the "added secret" to be mixed in to the content hash key.  If you want traditional convergent encryption behavior, set the added secret to be the empty string.

This patch also renames "content hash key" to "convergent encryption" in a argument names and variable names.  (A different and larger renaming is needed in order to clarify that Tahoe supports immutable files which are not encrypted content-hash-key a.k.a. convergent encryption.)

This patch also changes a few unit tests to use non-convergent encryption, because it doesn't matter for what they are testing and non-convergent encryption is slightly faster.

15 files changed:
src/allmydata/client.py
src/allmydata/control.py
src/allmydata/interfaces.py
src/allmydata/test/check_memory.py
src/allmydata/test/test_dirnode.py
src/allmydata/test/test_encode.py
src/allmydata/test/test_helper.py
src/allmydata/test/test_system.py
src/allmydata/test/test_upload.py
src/allmydata/test/test_util.py
src/allmydata/test/test_web.py
src/allmydata/upload.py
src/allmydata/util/hashutil.py
src/allmydata/web/unlinked.py
src/allmydata/webish.py

index ba857d7717f10eac4d3b052b86ae0a10790bdcb8..94880ec223da939c2c1eb71af441f2bf924a7cae 100644 (file)
@@ -34,6 +34,9 @@ PiB=1024*TiB
 class StubClient(Referenceable):
     implements(RIStubClient)
 
+def _make_secret():
+    return base32.b2a(os.urandom(hashutil.CRYPTO_VAL_SIZE)) + "\n"
+
 class Client(node.Node, testutil.PollMixin):
     PORTNUMFILE = "client.port"
     STOREDIR = 'storage'
@@ -103,9 +106,7 @@ class Client(node.Node, testutil.PollMixin):
             self.stats_provider = None
 
     def init_lease_secret(self):
-        def make_secret():
-            return base32.b2a(os.urandom(hashutil.CRYPTO_VAL_SIZE)) + "\n"
-        secret_s = self.get_or_create_private_config("secret", make_secret)
+        secret_s = self.get_or_create_private_config("secret", _make_secret)
         self._lease_secret = base32.a2b(secret_s)
 
     def init_storage(self):
@@ -151,6 +152,8 @@ class Client(node.Node, testutil.PollMixin):
 
     def init_client(self):
         helper_furl = self.get_config("helper.furl")
+        convergence_s = self.get_or_create_private_config('convergence', _make_secret)
+        self.convergence = base32.a2b(convergence_s)
         self.add_service(Uploader(helper_furl))
         self.add_service(Downloader())
         self.add_service(Checker())
index cd9d3cee106e870e425dbb277eef2574cebc9a89..99a3139b144f44c7b94bbe20ca1f57b07e026d14 100644 (file)
@@ -42,9 +42,9 @@ class ControlServer(Referenceable, service.Service, testutil.PollMixin):
     def remote_wait_for_client_connections(self, num_clients):
         return self.parent.debug_wait_for_client_connections(num_clients)
 
-    def remote_upload_from_file_to_uri(self, filename):
+    def remote_upload_from_file_to_uri(self, filename, convergence):
         uploader = self.parent.getServiceNamed("uploader")
-        u = upload.FileName(filename)
+        u = upload.FileName(filename, convergence=convergence)
         d = uploader.upload(u)
         d.addCallback(lambda results: results.uri)
         return d
@@ -161,7 +161,7 @@ class SpeedTest:
                 d1 = self._n.overwrite(data)
                 d1.addCallback(lambda res: self._n.get_uri())
             else:
-                up = upload.FileName(fn)
+                up = upload.FileName(fn, convergence=None)
                 d1 = self.parent.upload(up)
                 d1.addCallback(lambda results: results.uri)
             d1.addCallback(_record_uri, i)
index d438de7b5ab366d32e9c6367fcc3ff18aa62075d..190288a0c3d315e3321e38584b70d2b38042ccb5 100644 (file)
@@ -1578,11 +1578,14 @@ class RIControlClient(RemoteInterface):
         storage servers.
         """
 
-    def upload_from_file_to_uri(filename=str):
+    def upload_from_file_to_uri(filename=str, convergence=ChoiceOf(None, StringConstraint(2**20))):
         """Upload a file to the grid. This accepts a filename (which must be
-        absolute) that points to a file on the node's local disk. The node
-        will read the contents of this file, upload it to the grid, then
-        return the URI at which it was uploaded.
+        absolute) that points to a file on the node's local disk. The node will
+        read the contents of this file, upload it to the grid, then return the
+        URI at which it was uploaded.  If convergence is None then a random
+        encryption key will be used, else the plaintext will be hashed, then
+        that hash will be mixed together with the "convergence" string to form
+        the encryption key.
         """
         return URI
 
index de65de51000a98be05ac2b8df825d586ff46ee6c..c01769e78b9cc7506e5f3945ffe53844a2c40e84 100644 (file)
@@ -367,7 +367,7 @@ this file are ignored.
         if self.mode in ("upload", "upload-self"):
             files[name] = self.create_data(name, size)
             d = self.control_rref.callRemote("upload_from_file_to_uri",
-                                             files[name])
+                                             files[name], convergence=None)
             def _done(uri):
                 os.remove(files[name])
                 del files[name]
index 9d053dc89f4e269bb8b08a7c387cd97342672f09..dafc67af2c651b604d5c51293e2646477a5ec310 100644 (file)
@@ -120,7 +120,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
     def test_readonly(self):
         fileuri = make_chk_file_uri(1234)
         filenode = self.client.create_node_from_uri(fileuri)
-        uploadable = upload.Data("some data")
+        uploadable = upload.Data("some data", convergence="some convergence string")
 
         d = self.client.create_empty_dirnode()
         def _created(rw_dn):
@@ -338,7 +338,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
             # hundrededths of a second.
             d.addCallback(self.stall, 0.1)
             d.addCallback(lambda res: n.add_file(u"timestamps",
-                                                 upload.Data("stamp me")))
+                                                 upload.Data("stamp me", convergence="some convergence string")))
             d.addCallback(self.stall, 0.1)
             def _stop(res):
                 self._stop_timestamp = time.time()
@@ -393,7 +393,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
                           self.failUnlessEqual(sorted(children.keys()),
                                                sorted([u"child"])))
 
-            uploadable = upload.Data("some data")
+            uploadable = upload.Data("some data", convergence="some convergence string")
             d.addCallback(lambda res: n.add_file(u"newfile", uploadable))
             d.addCallback(lambda newnode:
                           self.failUnless(IFileNode.providedBy(newnode)))
@@ -406,7 +406,7 @@ class Dirnode(unittest.TestCase, testutil.ShouldFailMixin):
                           self.failUnlessEqual(sorted(metadata.keys()),
                                                ["ctime", "mtime"]))
 
-            uploadable = upload.Data("some data")
+            uploadable = upload.Data("some data", convergence="some convergence string")
             d.addCallback(lambda res: n.add_file(u"newfile-metadata",
                                                  uploadable,
                                                  {"key": "value"}))
index 4b729456e3eef1a9bd3f3e35f1b4943a25328cb4..fea99e5a920a64f4b9c8c9b840246e8aa5725564 100644 (file)
@@ -168,7 +168,7 @@ class Encode(unittest.TestCase):
         data = make_data(datalen)
         # force use of multiple segments
         e = encode.Encoder()
-        u = upload.Data(data)
+        u = upload.Data(data, convergence="some convergence string")
         u.max_segment_size = max_segment_size
         u.encoding_param_k = 25
         u.encoding_param_happy = 75
@@ -303,7 +303,7 @@ class Roundtrip(unittest.TestCase):
         if AVAILABLE_SHARES is None:
             AVAILABLE_SHARES = NUM_SHARES
         e = encode.Encoder()
-        u = upload.Data(data)
+        u = upload.Data(data, convergence="some convergence string")
         # force use of multiple segments by using a low max_segment_size
         u.max_segment_size = max_segment_size
         u.encoding_param_k = k
index 9b0d0b0ce80a091f5ba74b6910953a3b992f945c..9470a0dc7b47e28773366a94168bf0b7bfcecc70 100644 (file)
@@ -72,8 +72,8 @@ def flush_but_dont_ignore(res):
     d.addCallback(_done)
     return d
 
-def upload_data(uploader, data):
-    u = upload.Data(data)
+def upload_data(uploader, data, convergence):
+    u = upload.Data(data, convergence=convergence)
     return uploader.upload(u)
 
 class AssistedUpload(unittest.TestCase):
@@ -116,7 +116,7 @@ class AssistedUpload(unittest.TestCase):
         def _ready(res):
             assert u._helper
 
-            return upload_data(u, DATA)
+            return upload_data(u, DATA, convergence="some convergence string")
         d.addCallback(_ready)
         def _uploaded(results):
             uri = results.uri
@@ -149,7 +149,7 @@ class AssistedUpload(unittest.TestCase):
         # this must be a multiple of 'required_shares'==k
         segsize = mathutil.next_multiple(segsize, k)
 
-        key = hashutil.content_hash_key_hash(k, n, segsize, DATA)
+        key = hashutil.convergence_hash(k, n, segsize, DATA, "test convergence string")
         assert len(key) == 16
         encryptor = AES(key)
         SI = hashutil.storage_index_hash(key)
@@ -169,7 +169,7 @@ class AssistedUpload(unittest.TestCase):
 
         def _ready(res):
             assert u._helper
-            return upload_data(u, DATA)
+            return upload_data(u, DATA, convergence="test convergence string")
         d.addCallback(_ready)
         def _uploaded(results):
             uri = results.uri
@@ -200,7 +200,7 @@ class AssistedUpload(unittest.TestCase):
         def _ready(res):
             assert u._helper
 
-            return upload_data(u, DATA)
+            return upload_data(u, DATA, convergence="some convergence string")
         d.addCallback(_ready)
         def _uploaded(results):
             uri = results.uri
index 33a4bc0600ac72a79a86b4932f99c2300f94e83a..77092d802d51d2ae21a0a7c43f6a543746dc5d23 100644 (file)
@@ -256,15 +256,15 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
 
     def test_upload_and_download_random_key(self):
         self.basedir = "system/SystemTest/test_upload_and_download_random_key"
-        return self._test_upload_and_download(False)
+        return self._test_upload_and_download(convergence=None)
     test_upload_and_download_random_key.timeout = 4800
 
-    def test_upload_and_download_content_hash_key(self):
-        self.basedir = "system/SystemTest/test_upload_and_download_CHK"
-        return self._test_upload_and_download(True)
-    test_upload_and_download_content_hash_key.timeout = 4800
+    def test_upload_and_download_convergent(self):
+        self.basedir = "system/SystemTest/test_upload_and_download_convergent"
+        return self._test_upload_and_download(convergence="some convergence string")
+    test_upload_and_download_convergent.timeout = 4800
 
-    def _test_upload_and_download(self, contenthashkey):
+    def _test_upload_and_download(self, convergence):
         # we use 4000 bytes of data, which will result in about 400k written
         # to disk among all our simulated nodes
         DATA = "Some data to upload\n" * 200
@@ -287,7 +287,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
             # tail segment is not the same length as the others. This actualy
             # gets rounded up to 1025 to be a multiple of the number of
             # required shares (since we use 25 out of 100 FEC).
-            up = upload.Data(DATA, contenthashkey=contenthashkey)
+            up = upload.Data(DATA, convergence=convergence)
             up.max_segment_size = 1024
             d1 = u.upload(up)
             return d1
@@ -301,12 +301,12 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
         d.addCallback(_upload_done)
 
         def _upload_again(res):
-            # Upload again. If contenthashkey then this ought to be
+            # Upload again. If using convergent encryption then this ought to be
             # short-circuited, however with the way we currently generate URIs
             # (i.e. because they include the roothash), we have to do all of the
             # encoding work, and only get to save on the upload part.
             log.msg("UPLOADING AGAIN")
-            up = upload.Data(DATA, contenthashkey=contenthashkey)
+            up = upload.Data(DATA, convergence=convergence)
             up.max_segment_size = 1024
             d1 = self.uploader.upload(up)
         d.addCallback(_upload_again)
@@ -372,7 +372,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
 
         HELPER_DATA = "Data that needs help to upload" * 1000
         def _upload_with_helper(res):
-            u = upload.Data(HELPER_DATA, contenthashkey=contenthashkey)
+            u = upload.Data(HELPER_DATA, convergence=convergence)
             d = self.extra_node.upload(u)
             def _uploaded(results):
                 uri = results.uri
@@ -385,7 +385,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
         d.addCallback(_upload_with_helper)
 
         def _upload_duplicate_with_helper(res):
-            u = upload.Data(HELPER_DATA, contenthashkey=contenthashkey)
+            u = upload.Data(HELPER_DATA, convergence=convergence)
             u.debug_stash_RemoteEncryptedUploadable = True
             d = self.extra_node.upload(u)
             def _uploaded(results):
@@ -398,13 +398,13 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
                             "uploadable started uploading, should have been avoided")
             d.addCallback(_check)
             return d
-        if contenthashkey:
+        if convergence is not None:
             d.addCallback(_upload_duplicate_with_helper)
 
         def _upload_resumable(res):
             DATA = "Data that needs help to upload and gets interrupted" * 1000
-            u1 = CountingDataUploadable(DATA, contenthashkey=contenthashkey)
-            u2 = CountingDataUploadable(DATA, contenthashkey=contenthashkey)
+            u1 = CountingDataUploadable(DATA, convergence=convergence)
+            u2 = CountingDataUploadable(DATA, convergence=convergence)
 
             # we interrupt the connection after about 5kB by shutting down
             # the helper, then restartingit.
@@ -490,7 +490,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
                 # to store the key locally and re-use it on the next upload of
                 # this file, which isn't a bad thing to do, but we currently
                 # don't do it.)
-                if contenthashkey:
+                if convergence is not None:
                     # Make sure we did not have to read the whole file the
                     # second time around .
                     self.failUnless(bytes_sent < len(DATA),
@@ -510,9 +510,9 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
 
             def _check(newdata):
                 self.failUnlessEqual(newdata, DATA)
-                # If using a content hash key, then also check that the helper
-                # has removed the temp file from its directories.
-                if contenthashkey:
+                # If using convergent encryption, then also check that the
+                # helper has removed the temp file from its directories.
+                if convergence is not None:
                     basedir = os.path.join(self.getdir("client0"), "helper")
                     files = os.listdir(os.path.join(basedir, "CHK_encoding"))
                     self.failUnlessEqual(files, [])
@@ -890,7 +890,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
         return d
 
     def _do_publish1(self, res):
-        ut = upload.Data(self.data)
+        ut = upload.Data(self.data, convergence=None)
         c0 = self.clients[0]
         d = c0.create_empty_dirnode()
         def _made_root(new_dirnode):
@@ -910,7 +910,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
         return d
 
     def _do_publish2(self, res):
-        ut = upload.Data(self.data)
+        ut = upload.Data(self.data, convergence=None)
         d = self._subdir1_node.create_empty_directory(u"subdir2")
         d.addCallback(lambda subdir2: subdir2.add_file(u"mydata992", ut))
         return d
@@ -927,7 +927,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
 
     def _do_publish_private(self, res):
         self.smalldata = "sssh, very secret stuff"
-        ut = upload.Data(self.smalldata)
+        ut = upload.Data(self.smalldata, convergence=None)
         d = self.clients[0].create_empty_dirnode()
         d.addCallback(self.log, "GOT private directory")
         def _got_new_dir(privnode):
@@ -1009,7 +1009,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
             d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "mkdir(nope)", None, dirnode.create_empty_directory, u"nope"))
 
             d1.addCallback(self.log, "doing add_file(ro)")
-            ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.")
+            ut = upload.Data("I will disappear, unrecorded and unobserved. The tragedy of my demise is made more poignant by its silence, but this beauty is not for you to ever know.", convergence="99i-p1x4-xd4-18yc-ywt-87uu-msu-zo -- completely and totally unguessable string (unless you read this)")
             d1.addCallback(lambda res: self.shouldFail2(NotMutableError, "add_file(nope)", None, dirnode.add_file, u"hope", ut))
 
             d1.addCallback(self.log, "doing get(ro)")
@@ -1345,7 +1345,7 @@ class SystemTest(testutil.SignalMixin, testutil.PollMixin, unittest.TestCase):
         d.addCallback(self._test_control2, control_furl_file)
         return d
     def _test_control2(self, rref, filename):
-        d = rref.callRemote("upload_from_file_to_uri", filename)
+        d = rref.callRemote("upload_from_file_to_uri", filename, convergence=None)
         downfile = os.path.join(self.basedir, "control.downfile")
         d.addCallback(lambda uri:
                       rref.callRemote("download_from_uri_to_file",
index 91f0e5a5d82270804427f5ba9ca98c2d9ad6e31d..e15731a32e1df5f0ecdcddf358c56e33d991242d 100644 (file)
@@ -25,14 +25,14 @@ class Uploadable(unittest.TestCase):
         self.failUnlessEqual(s, expected)
 
     def test_filehandle_random_key(self):
-        return self._test_filehandle(True)
+        return self._test_filehandle(convergence=None)
 
-    def test_filehandle_content_hash_key(self):
-        return self._test_filehandle(False)
+    def test_filehandle_convergent_encryption(self):
+        return self._test_filehandle(convergence="some convergence string")
 
-    def _test_filehandle(self, randomkey):
+    def _test_filehandle(self, convergence):
         s = StringIO("a"*41)
-        u = upload.FileHandle(s, randomkey)
+        u = upload.FileHandle(s, convergence=convergence)
         d = u.get_size()
         d.addCallback(self.failUnlessEqual, 41)
         d.addCallback(lambda res: u.read(1))
@@ -50,7 +50,7 @@ class Uploadable(unittest.TestCase):
         f = open(fn, "w")
         f.write("a"*41)
         f.close()
-        u = upload.FileName(fn)
+        u = upload.FileName(fn, convergence=None)
         d = u.get_size()
         d.addCallback(self.failUnlessEqual, 41)
         d.addCallback(lambda res: u.read(1))
@@ -62,7 +62,7 @@ class Uploadable(unittest.TestCase):
 
     def test_data(self):
         s = "a"*41
-        u = upload.Data(s)
+        u = upload.Data(s, convergence=None)
         d = u.get_size()
         d.addCallback(self.failUnlessEqual, 41)
         d.addCallback(lambda res: u.read(1))
@@ -169,13 +169,13 @@ SIZE_SMALL = 16
 SIZE_LARGE = len(DATA)
 
 def upload_data(uploader, data):
-    u = upload.Data(data)
+    u = upload.Data(data, convergence=None)
     return uploader.upload(u)
 def upload_filename(uploader, filename):
-    u = upload.FileName(filename)
+    u = upload.FileName(filename, convergence=None)
     return uploader.upload(u)
 def upload_filehandle(uploader, fh):
-    u = upload.FileHandle(fh)
+    u = upload.FileHandle(fh, convergence=None)
     return uploader.upload(u)
 
 class GoodServer(unittest.TestCase):
@@ -444,38 +444,57 @@ class PeerSelection(unittest.TestCase):
 class StorageIndex(unittest.TestCase):
     def test_params_must_matter(self):
         DATA = "I am some data"
-        u = upload.Data(DATA)
+        u = upload.Data(DATA, convergence="")
         eu = upload.EncryptAnUploadable(u)
         d1 = eu.get_storage_index()
 
         # CHK means the same data should encrypt the same way
-        u = upload.Data(DATA)
+        u = upload.Data(DATA, convergence="")
         eu = upload.EncryptAnUploadable(u)
         d1a = eu.get_storage_index()
 
-        # but if we change the encoding parameters, it should be different
-        u = upload.Data(DATA)
+        # but if we use a different convergence string it should be different
+        u = upload.Data(DATA, convergence="wheee!")
+        eu = upload.EncryptAnUploadable(u)
+        d1salt1 = eu.get_storage_index()
+
+        # and if we add yet a different convergence it should be different again
+        u = upload.Data(DATA, convergence="NOT wheee!")
+        eu = upload.EncryptAnUploadable(u)
+        d1salt2 = eu.get_storage_index()
+
+        # and if we use the first string again it should be the same as last time
+        u = upload.Data(DATA, convergence="wheee!")
+        eu = upload.EncryptAnUploadable(u)
+        d1salt1a = eu.get_storage_index()
+
+        # and if we change the encoding parameters, it should be different (from the same convergence string with different encoding parameters)
+        u = upload.Data(DATA, convergence="")
         u.encoding_param_k = u.default_encoding_param_k + 1
         eu = upload.EncryptAnUploadable(u)
         d2 = eu.get_storage_index()
 
         # and if we use a random key, it should be different than the CHK
-        u = upload.Data(DATA, contenthashkey=False)
+        u = upload.Data(DATA, convergence=None)
         eu = upload.EncryptAnUploadable(u)
         d3 = eu.get_storage_index()
         # and different from another instance
-        u = upload.Data(DATA, contenthashkey=False)
+        u = upload.Data(DATA, convergence=None)
         eu = upload.EncryptAnUploadable(u)
         d4 = eu.get_storage_index()
 
-        d = DeferredListShouldSucceed([d1,d1a,d2,d3,d4])
+        d = DeferredListShouldSucceed([d1,d1a,d1salt1,d1salt2,d1salt1a,d2,d3,d4])
         def _done(res):
-            si1, si1a, si2, si3, si4 = res
+            si1, si1a, si1salt1, si1salt2, si1salt1a, si2, si3, si4 = res
             self.failUnlessEqual(si1, si1a)
             self.failIfEqual(si1, si2)
             self.failIfEqual(si1, si3)
             self.failIfEqual(si1, si4)
             self.failIfEqual(si3, si4)
+            self.failIfEqual(si1salt1, si1)
+            self.failIfEqual(si1salt1, si1salt2)
+            self.failIfEqual(si1salt2, si1)
+            self.failUnlessEqual(si1salt1, si1salt1a)
         d.addCallback(_done)
         return d
 
index d80f09034e3d961e2437f31520f7fc4caf1bf54d..98d6da71b9f72ad8f2e22d5e67ee1b2ac4c36460 100644 (file)
@@ -408,8 +408,8 @@ class HashUtilTests(unittest.TestCase):
         self.failUnlessEqual(h1, h2)
 
     def test_chk(self):
-        h1 = hashutil.content_hash_key_hash(3, 10, 1000, "data")
-        h2 = hashutil.content_hash_key_hasher(3, 10, 1000)
+        h1 = hashutil.convergence_hash(3, 10, 1000, "data", "secret")
+        h2 = hashutil.convergence_hasher(3, 10, 1000, "secret")
         h2.update("data")
         h2 = h2.digest()
         self.failUnlessEqual(h1, h2)
index 5711af92589c1bd087b892b81c7475ce4418b099..a55b9efb7550efa147923a52d50d5da67a848268 100644 (file)
@@ -35,6 +35,7 @@ class FakeClient(service.MultiService):
     introducer_client = FakeIntroducerClient()
     _all_upload_status = [upload.UploadStatus()]
     _all_download_status = [download.DownloadStatus()]
+    convergence = "some random string"
 
     def connected_to_introducer(self):
         return False
index a44ecdb875fa3824451b8d077d2e7c64d3a78df3..384aea2b021848b626728b58528ab06737cf6263 100644 (file)
@@ -11,7 +11,7 @@ from foolscap.logging import log
 from allmydata.util.hashutil import file_renewal_secret_hash, \
      file_cancel_secret_hash, bucket_renewal_secret_hash, \
      bucket_cancel_secret_hash, plaintext_hasher, \
-     storage_index_hash, plaintext_segment_hasher, content_hash_key_hasher
+     storage_index_hash, plaintext_segment_hasher, convergence_hasher
 from allmydata import encode, storage, hashtree, uri
 from allmydata.util import base32, idlib, mathutil
 from allmydata.util.assertutil import precondition
@@ -1084,13 +1084,20 @@ class BaseUploadable:
 class FileHandle(BaseUploadable):
     implements(IUploadable)
 
-    def __init__(self, filehandle, contenthashkey=True):
+    def __init__(self, filehandle, convergence):
+        """
+        Upload the data from the filehandle.  If convergence is None then a
+        random encryption key will be used, else the plaintext will be hashed,
+        then the hash will be hashed together with the string in the
+        "convergence" argument to form the encryption key."
+        """
+        assert convergence is None or isinstance(convergence, str), (convergence, type(convergence))
         self._filehandle = filehandle
         self._key = None
-        self._contenthashkey = contenthashkey
+        self.convergence = convergence
         self._size = None
 
-    def _get_encryption_key_content_hash(self):
+    def _get_encryption_key_convergent(self):
         if self._key is not None:
             return defer.succeed(self._key)
 
@@ -1100,7 +1107,7 @@ class FileHandle(BaseUploadable):
         def _got(params):
             k, happy, n, segsize = params
             f = self._filehandle
-            enckey_hasher = content_hash_key_hasher(k, n, segsize)
+            enckey_hasher = convergence_hasher(k, n, segsize, self.convergence)
             f.seek(0)
             BLOCKSIZE = 64*1024
             bytes_read = 0
@@ -1131,8 +1138,8 @@ class FileHandle(BaseUploadable):
         return defer.succeed(self._key)
 
     def get_encryption_key(self):
-        if self._contenthashkey:
-            return self._get_encryption_key_content_hash()
+        if self.convergence is not None:
+            return self._get_encryption_key_convergent()
         else:
             return self._get_encryption_key_random()
 
@@ -1153,15 +1160,29 @@ class FileHandle(BaseUploadable):
         pass
 
 class FileName(FileHandle):
-    def __init__(self, filename, contenthashkey=True):
-        FileHandle.__init__(self, open(filename, "rb"), contenthashkey=contenthashkey)
+    def __init__(self, filename, convergence):
+        """
+        Upload the data from the filename.  If convergence is None then a
+        random encryption key will be used, else the plaintext will be hashed,
+        then the hash will be hashed together with the string in the
+        "convergence" argument to form the encryption key."
+        """
+        assert convergence is None or isinstance(convergence, str), (convergence, type(convergence))
+        FileHandle.__init__(self, open(filename, "rb"), convergence=convergence)
     def close(self):
         FileHandle.close(self)
         self._filehandle.close()
 
 class Data(FileHandle):
-    def __init__(self, data, contenthashkey=True):
-        FileHandle.__init__(self, StringIO(data), contenthashkey=contenthashkey)
+    def __init__(self, data, convergence):
+        """
+        Upload the data from the data argument.  If convergence is None then a
+        random encryption key will be used, else the plaintext will be hashed,
+        then the hash will be hashed together with the string in the
+        "convergence" argument to form the encryption key."
+        """
+        assert convergence is None or isinstance(convergence, str), (convergence, type(convergence))
+        FileHandle.__init__(self, StringIO(data), convergence=convergence)
 
 class Uploader(service.MultiService):
     """I am a service that allows file uploading. I am a service-child of the
index add0a0f18258866e31c3d66c29a5dee12c275a5b..4e762ecf31376da03d3be46731ae8f3560e8e09b 100644 (file)
@@ -68,7 +68,7 @@ PLAINTEXT_TAG = "allmydata_plaintext_v1"
 CIPHERTEXT_TAG = "allmydata_crypttext_v1"
 CIPHERTEXT_SEGMENT_TAG = "allmydata_crypttext_segment_v1"
 PLAINTEXT_SEGMENT_TAG = "allmydata_plaintext_segment_v1"
-CONTENT_HASH_KEY_TAG = "allmydata_immutable_content_to_key_v1+"
+CONVERGENT_ENCRYPTION_TAG = "allmydata_immutable_content_to_key_with_added_secret_v1+"
 
 CLIENT_RENEWAL_TAG = "allmydata_client_renewal_secret_v1"
 CLIENT_CANCEL_TAG = "allmydata_client_cancel_secret_v1"
@@ -91,9 +91,9 @@ DIRNODE_CHILD_WRITECAP_TAG = "allmydata_mutable_writekey_and_salt_to_dirnode_chi
 
 def storage_index_hash(key):
     # storage index is truncated to 128 bits (16 bytes). We're only hashing a
-    # 16-byte value to get it, so there's no point in using a larger value.
-    # We use this same tagged hash to go from encryption key to storage index
-    # for random-keyed immutable files and content-hash-keyed immutabie
+    # 16-byte value to get it, so there's no point in using a larger value.  We
+    # use this same tagged hash to go from encryption key to storage index for
+    # random-keyed immutable files and convergent-encryption immutabie
     # files. Mutable files use ssk_storage_index_hash().
     return tagged_hash(STORAGE_INDEX_TAG, key, 16)
 
@@ -129,15 +129,14 @@ def plaintext_segment_hasher():
 
 KEYLEN = 16
 
-def content_hash_key_hash(k, n, segsize, data):
-    # This is defined to return a 16-byte AES key.
+def convergence_hash(k, n, segsize, data, convergence):
+    h = convergence_hasher(k, n, segsize, convergence)
+    h.update(data)
+    return h.digest()
+def convergence_hasher(k, n, segsize, convergence):
+    assert isinstance(convergence, str)
     param_tag = netstring("%d,%d,%d" % (k, n, segsize))
-    tag = CONTENT_HASH_KEY_TAG + param_tag
-    h = tagged_hash(tag, data, KEYLEN)
-    return h
-def content_hash_key_hasher(k, n, segsize):
-    param_tag = netstring("%d,%d,%d" % (k, n, segsize))
-    tag = CONTENT_HASH_KEY_TAG + param_tag
+    tag = CONVERGENT_ENCRYPTION_TAG + netstring(convergence) + param_tag
     return tagged_hasher(tag, KEYLEN)
 
 def random_key():
index 696ee84712364f7ed6acf933a9ce8ef541bc3f13..7e4ae6550c3d93bbad73b20c4c56fd6462668089 100644 (file)
@@ -14,8 +14,10 @@ class UnlinkedPUTCHKUploader(rend.Page):
         # "PUT /uri", to create an unlinked file. This is like PUT but
         # without the associated set_uri.
 
-        uploadable = FileHandle(req.content)
-        d = IClient(ctx).upload(uploadable)
+        client = IClient(ctx)
+
+        uploadable = FileHandle(req.content, client.convergence)
+        d = client.upload(uploadable)
         d.addCallback(lambda results: results.uri)
         # that fires with the URI of the new file
         return d
@@ -52,7 +54,7 @@ class UnlinkedPOSTCHKUploader(status.UploadResultsRendererMixin, rend.Page):
         assert req.method == "POST"
         self._done = observer.OneShotObserverList()
         fileobj = req.fields["file"].file
-        uploadable = FileHandle(fileobj)
+        uploadable = FileHandle(fileobj, client.convergence)
         d = client.upload(uploadable)
         d.addBoth(self._done.fire)
 
index 35834a29adaf30d6301fb99725ca8a9b9dcc2443..3b6485d120cfef02e8437461d8e7a92b7d11bb36 100644 (file)
@@ -867,7 +867,7 @@ class POSTHandler(rend.Page):
                 return d2
             d.addCallback(_checked)
         else:
-            uploadable = FileHandle(contents.file)
+            uploadable = FileHandle(contents.file, convergence=client.convergence)
             d = self._check_replacement(name)
             d.addCallback(lambda res: self._node.add_file(name, uploadable))
             def _done(newnode):
@@ -1047,6 +1047,7 @@ class PUTHandler(rend.Page):
         self._replace = replace
 
     def renderHTTP(self, ctx):
+        client = IClient(ctx)
         req = inevow.IRequest(ctx)
         t = self._t
         localfile = self._localfile
@@ -1063,18 +1064,18 @@ class PUTHandler(rend.Page):
         d.addCallback(self._check_replacement, name, self._replace)
         if t == "upload":
             if localfile:
-                d.addCallback(self._upload_localfile, localfile, name)
+                d.addCallback(self._upload_localfile, localfile, name, convergence=client.convergence)
             else:
                 # localdir
                 # take the last step
                 d.addCallback(self._get_or_create_directories, self._path[-1:])
-                d.addCallback(self._upload_localdir, localdir)
+                d.addCallback(self._upload_localdir, localdir, convergence=client.convergence)
         elif t == "uri":
             d.addCallback(self._attach_uri, req.content, name)
         elif t == "mkdir":
             d.addCallback(self._mkdir, name)
         else:
-            d.addCallback(self._upload_file, req.content, name)
+            d.addCallback(self._upload_file, req.content, name, convergence=client.convergence)
 
         def _transform_error(f):
             errors = {BlockingFileError: http.BAD_REQUEST,
@@ -1126,8 +1127,8 @@ class PUTHandler(rend.Page):
         d.addCallback(_done)
         return d
 
-    def _upload_file(self, node, contents, name):
-        uploadable = FileHandle(contents)
+    def _upload_file(self, node, contents, name, convergence):
+        uploadable = FileHandle(contents, convergence=convergence)
         d = node.add_file(name, uploadable)
         def _done(filenode):
             log.msg("webish upload complete",
@@ -1136,8 +1137,8 @@ class PUTHandler(rend.Page):
         d.addCallback(_done)
         return d
 
-    def _upload_localfile(self, node, localfile, name):
-        uploadable = FileName(localfile)
+    def _upload_localfile(self, node, localfile, name, convergence):
+        uploadable = FileName(localfile, convergence=convergence)
         d = node.add_file(name, uploadable)
         d.addCallback(lambda filenode: filenode.get_uri())
         return d
@@ -1150,7 +1151,7 @@ class PUTHandler(rend.Page):
         d.addCallback(_done)
         return d
 
-    def _upload_localdir(self, node, localdir):
+    def _upload_localdir(self, node, localdir, convergence):
         # build up a list of files to upload. TODO: for now, these files and
         # directories must have UTF-8 encoded filenames: anything else will
         # cause the upload to break.
@@ -1179,7 +1180,7 @@ class PUTHandler(rend.Page):
             if dir:
                 d.addCallback(self._makedir, node, dir)
         for f in all_files:
-            d.addCallback(self._upload_one_file, node, localdir, f)
+            d.addCallback(self._upload_one_file, node, localdir, f, convergence=convergence)
         return d
 
     def _makedir(self, res, node, dir):
@@ -1191,12 +1192,12 @@ class PUTHandler(rend.Page):
         d.addCallback(lambda parent: parent.create_empty_directory(dir[-1]))
         return d
 
-    def _upload_one_file(self, res, node, localdir, f):
+    def _upload_one_file(self, res, node, localdir, f, convergence):
         # get the parent. We can be sure this exists because we already
         # went through and created all the directories we require.
         localfile = os.path.join(localdir, *f)
         d = node.get_child_at_path(f[:-1])
-        d.addCallback(self._upload_localfile, localfile, f[-1])
+        d.addCallback(self._upload_localfile, localfile, f[-1], convergence=convergence)
         return d