diff --git a/src/_secureaccesstokenauthorizer/_storage_client.py b/src/_secureaccesstokenauthorizer/_storage_client.py
index 4560c06266646e19c7b2e14095789de000ff6dd6..f1df27490e70e7c8a6938e7ca6aa0b52b3c2a177 100644
--- a/src/_secureaccesstokenauthorizer/_storage_client.py
+++ b/src/_secureaccesstokenauthorizer/_storage_client.py
@@ -76,3 +76,17 @@ class SecureAccessTokenAuthorizerStorageClient(object):
             "get_buckets",
             storage_index,
         )
+
+    def add_lease(
+            self,
+            storage_index,
+            renew_secret,
+            cancel_secret,
+    ):
+        return self._rref.callRemote(
+            "add_lease",
+            self._get_tokens(),
+            storage_index,
+            renew_secret,
+            cancel_secret,
+        )
diff --git a/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py b/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py
index e19d67725d869fd770f702340f12e1bbbec5631f..96369af1c7b4e2249028d65bf07f4a2bed79f03f 100644
--- a/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py
+++ b/src/_secureaccesstokenauthorizer/tests/test_storage_protocol.py
@@ -17,6 +17,10 @@ Tests for communication between the client and server components.
 """
 import attr
 
+from struct import (
+    unpack,
+)
+
 from fixtures import (
     Fixture,
     TempDir,
@@ -26,6 +30,7 @@ from testtools import (
 )
 from testtools.matchers import (
     Equals,
+    HasLength,
 )
 from testtools.twistedsupport._deferred import (
     # I'd rather use https://twistedmatrix.com/trac/ticket/8900 but efforts
@@ -35,8 +40,15 @@ from testtools.twistedsupport._deferred import (
 
 from hypothesis import (
     given,
+    assume,
+)
+from hypothesis.strategies import (
+    tuples,
 )
 
+from twisted.python.filepath import (
+    FilePath,
+)
 from twisted.internet.defer import (
     execute,
 )
@@ -53,6 +65,7 @@ from .strategies import (
     storage_indexes,
     lease_renew_secrets,
     lease_cancel_secrets,
+    sharenums,
     sharenum_sets,
     sizes,
 )
@@ -97,6 +110,23 @@ class ImmutableTests(TestCase):
     """
     Tests for interaction with immutable shares.
     """
+    def setUp(self):
+        super(ImmutableTests, self).setUp()
+        self.canary = LocalReferenceable(None)
+        self.anonymous_storage_server = self.useFixture(AnonymousStorageServer()).storage_server
+
+        def get_tokens():
+            return [u"x"]
+
+        self.server = SecureAccessTokenAuthorizerStorageServer(
+            self.anonymous_storage_server,
+        )
+        self.local_remote_server = LocalRemote(self.server)
+        self.client = SecureAccessTokenAuthorizerStorageClient(
+            get_rref=lambda: self.local_remote_server,
+            get_tokens=get_tokens,
+        )
+
     @given(
         storage_index=storage_indexes(),
         renew_secret=lease_renew_secrets(),
@@ -110,28 +140,18 @@ class ImmutableTests(TestCase):
         resulting buckets can be read back using *get_buckets* and methods of
         those resulting buckets.
         """
-        anonymous_storage_server = self.useFixture(AnonymousStorageServer()).storage_server
-
-        def get_tokens():
-            return [u"x"]
-
-        server = SecureAccessTokenAuthorizerStorageServer(
-            anonymous_storage_server,
-        )
-        local_remote_server = LocalRemote(server)
-        client = SecureAccessTokenAuthorizerStorageClient(
-            get_rref=lambda: local_remote_server,
-            get_tokens=get_tokens,
-        )
+        # Hypothesis causes our storage server to be used many times.  Clean
+        # up between iterations.
+        cleanup_storage_server(self.anonymous_storage_server)
 
         alreadygot, allocated = extract_result(
-            client.allocate_buckets(
+            self.client.allocate_buckets(
                 storage_index,
                 renew_secret,
                 cancel_secret,
                 sharenums,
                 size,
-                canary=LocalReferenceable(None),
+                canary=self.canary,
             ),
         )
         self.expectThat(
@@ -146,12 +166,10 @@ class ImmutableTests(TestCase):
         )
 
         for sharenum, bucket in allocated.items():
-            # returns None, nothing to extract
             bucket.remote_write(0, bytes_for_share(sharenum, size)),
-            # returns None, nothing to extract
             bucket.remote_close()
 
-        readers = extract_result(client.get_buckets(storage_index))
+        readers = extract_result(self.client.get_buckets(storage_index))
 
         self.expectThat(
             set(readers.keys()),
@@ -166,3 +184,64 @@ class ImmutableTests(TestCase):
                     sharenum,
                 ),
             )
+
+    @given(
+        storage_index=storage_indexes(),
+        renew_secrets=tuples(lease_renew_secrets(), lease_renew_secrets()),
+        cancel_secret=lease_cancel_secrets(),
+        sharenum=sharenums(),
+        size=sizes(),
+    )
+    def test_add_lease(self, storage_index, renew_secrets, cancel_secret, sharenum, size):
+        """
+        A lease can be added to an existing immutable share.
+        """
+        # Hypothesis causes our storage server to be used many times.  Clean
+        # up between iterations.
+        cleanup_storage_server(self.anonymous_storage_server)
+
+        # Use a different secret so that it's a new lease and not an
+        # implicit renewal.
+        add_lease_secret, renew_lease_secret = renew_secrets
+        assume(add_lease_secret != renew_lease_secret)
+
+        # Create a share we can toy with.
+        _, allocated = self.anonymous_storage_server.remote_allocate_buckets(
+            storage_index,
+            add_lease_secret,
+            cancel_secret,
+            {sharenum},
+            size,
+            canary=self.canary,
+        )
+        [(_, writer)] = allocated.items()
+        writer.remote_write(0, bytes_for_share(sharenum, size))
+        writer.remote_close()
+
+        extract_result(
+            self.client.add_lease(
+                storage_index,
+                renew_lease_secret,
+                cancel_secret,
+            ),
+        )
+
+        # It's hard to assert much about the lease without knowing about
+        # *some* implementation details of the storage server.  I prefer to
+        # know Python API details rather than on-disk format details.
+        [(_, reader)] = self.server.remote_get_buckets(storage_index).items()
+        leases = list(reader._share_file.get_leases())
+        self.assertThat(leases, HasLength(2))
+
+
+def cleanup_storage_server(storage_server):
+    """
+    Delete all of the shares held by the given storage server.
+
+    :param allmydata.storage.server.StorageServer storage_server: The storage
+        server with some on-disk shares to delete.
+    """
+    start = FilePath(storage_server.sharedir)
+    for p in start.walk():
+        if p is not start:
+            p.remove()