diff --git a/cabal.project b/cabal.project
index 55be17a13a683db9dd1f7743b243c5f56a0f587a..80fc512d3f3319213956e32b7fef370e50b09ec5 100644
--- a/cabal.project
+++ b/cabal.project
@@ -2,6 +2,7 @@ packages: .
           -- These aren't released on hackage yet so we have to grab them
           -- straight from the vcs host.  Probably should do some releases
           -- soon.
+          ../tahoe-ssk
           https://whetstone.private.storage/privatestorage/tahoe-chk/-/archive/0.1.0.1/tahoe-chk-0.1.0.1.tar.gz
           https://whetstone.private.storage/privatestorage/tahoe-great-black-swamp/-/archive/0.2.0.2/tahoe-great-black-swamp-0.2.0.2.tar.gz
 
diff --git a/gbs-downloader.cabal b/gbs-downloader.cabal
index 0312e51907c814bf571180adc41e61977864d352..e2ae392fa57145a3d2450382aad5ef0f9f5dcdc3 100644
--- a/gbs-downloader.cabal
+++ b/gbs-downloader.cabal
@@ -172,18 +172,23 @@ test-suite gbs-downloader-test
 
   -- Test dependencies.
   build-depends:
+    , asn1-encoding
+    , asn1-types
     , base
     , base32
     , binary
     , bytestring
     , containers
     , crypto-api
+    , cryptonite
     , data-default-class
     , gbs-downloader
     , hedgehog
     , tahoe-chk
+    , tahoe-ssk
     , tasty
     , tasty-hedgehog
     , tasty-hunit
     , text
+    , x509
     , yaml
diff --git a/test/Generators.hs b/test/Generators.hs
index 3adc7c662bbccce84fecc99c0cb6251bac5eb1fa..7827221f4e4200997a4ff55165e44577bdcb0af1 100644
--- a/test/Generators.hs
+++ b/test/Generators.hs
@@ -1,11 +1,19 @@
 module Generators where
 
+import qualified Crypto.PubKey.RSA as RSA
+import Data.ASN1.BinaryEncoding (DER (DER))
+import Data.ASN1.Encoding (ASN1Decoding (decodeASN1))
+import Data.ASN1.Types (ASN1Object (fromASN1))
+import Data.Bifunctor (Bifunctor (first))
 import Data.ByteString.Base32 (encodeBase32Unpadded)
+import qualified Data.ByteString.Lazy as LB
 import Data.Int (Int64)
 import qualified Data.Text as T
+import Data.X509 (PrivKey (PrivKeyRSA))
 import Hedgehog (MonadGen)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
+import System.IO.Unsafe (unsafePerformIO)
 import Tahoe.Announcement (Announcements (..), StorageServerAnnouncement (..))
 import Tahoe.CHK.Types (Parameters (..))
 
@@ -37,3 +45,30 @@ genStorageServerAnnouncements =
         <$> Gen.maybe (Gen.text (Range.linear 16 32) Gen.ascii)
         <*> Gen.maybe (Gen.text (Range.linear 16 32) Gen.ascii)
         <*> Gen.maybe (Gen.bytes $ Range.singleton 32)
+
+{- | Build RSA key pairs.
+
+ Because the specific bits of the key pair shouldn't make any difference to
+ any application logic, generating new RSA key pairs is expensive, and
+ generating new RSA key pairs in a way that makes sense in Hedgehog is
+ challenging, this implementation just knows a few RSA key pairs already and
+ will give back one of them.
+-}
+genRSAKeys :: MonadGen m => m RSA.PrivateKey
+genRSAKeys = Gen.element (map rsaKeyPair rsaKeyPairBytes)
+
+-- I'm not sure how to do IO in MonadGen so do the IO up front unsafely (but
+-- hopefully not really unsafely).
+rsaKeyPairBytes :: [LB.ByteString]
+{-# NOINLINE rsaKeyPairBytes #-}
+rsaKeyPairBytes = unsafePerformIO $ mapM (\n -> LB.readFile ("test/data/rsa-privkey-" <> show n <> ".der")) [0 .. 4 :: Int]
+
+rsaKeyPair :: LB.ByteString -> RSA.PrivateKey
+rsaKeyPair bs = do
+    let (Right kp) = do
+            asn1s <- first show (decodeASN1 DER bs)
+            (r, _) <- fromASN1 asn1s
+            case r of
+                PrivKeyRSA pk -> pure pk
+                _ -> error "Expected RSA Private Key"
+    kp
diff --git a/test/Spec.hs b/test/Spec.hs
index 91a9191fd9cb0223b4fb905c9838bf9d612c93df..73bb09a219d4f02ddb5406075cc229d2f0fed770 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -15,7 +15,7 @@ import qualified Data.Text as T
 import Data.Text.Encoding (encodeUtf8)
 import Data.Word (Word16)
 import qualified Data.Yaml as Yaml
-import Generators (genAnnouncements, genParameters)
+import Generators (genAnnouncements, genParameters, genRSAKeys)
 import Hedgehog (MonadGen, diff, forAll, property, tripping)
 import qualified Hedgehog.Gen as Gen
 import qualified Hedgehog.Range as Range
@@ -41,6 +41,9 @@ import Tahoe.Download (
     LookupServer,
     download,
  )
+import qualified Tahoe.SDMF as SDMF
+import Tahoe.SDMF.Internal.Capability (deriveReader)
+import qualified Tahoe.SDMF.Keys as SDMF.Keys
 import Tahoe.Server (memoryStorageServer)
 import Test.Tasty (TestTree, defaultMain, testGroup)
 import Test.Tasty.HUnit (assertEqual, testCase)
@@ -244,7 +247,7 @@ tests =
                 "download should fail with details about unreachable server"
                 (Left (NotEnoughDecodedShares{notEnoughDecodedSharesNeeded = 3, notEnoughDecodedSharesFound = 0}))
                 result
-        , testProperty "success" $
+        , testProperty "chk success" $
             property $ do
                 -- If we can recover enough distinct, decodeable shares from the
                 -- configured servers then we can recover the application data.
@@ -291,6 +294,27 @@ tests =
                 -- Recover the plaintext from the servers.
                 result <- liftIO $ download serverAnnouncements cap lookupServer
                 diff (Right plaintext) (==) result
+        , testCase "ssk success" $
+            property $ do
+                -- Like "chk success" above, but for SDMF (a case of SSK).
+                plaintext <- forAll $ BL.fromStrict <$> Gen.bytes (Range.exponential 56 1024)
+                sequenceNumber <- forAll $ Gen.integral (Range.exponential 1 10000)
+                keypair <- SDMF.Keys.KeyPair <$> forAll genRSAKeys
+                Parameters{paramRequiredShares = required, paramTotalShares = total} <- forAll genParameters
+
+                let ciphertext = SDMF.encrypt dataKey plaintext
+                (shares, writeCap) <- liftIO $ SDMF.encode keypair sequenceNumber required total ciphertext
+                let (Just readCap) = deriveReader
+                -- Distribute the shares.
+                liftIO $ placeShares cap (Binary.encode <$> shares) perServerShareCount servers 0
+
+                let serverMap = Map.fromList $ zip (Set.toList serverIDs') servers
+                    lookupServer = someServers serverMap
+                    serverAnnouncements = Map.fromSet makeAnn serverIDs'
+
+                -- Recover the plaintext from the servers.
+                result <- liftIO $ downloadMutable serverAnnouncements cap lookupServer
+                diff (Right plaintext) (==) result
         , testCase "immutable upload/download to using Great Black Swamp" $ do
             pure ()
             -- Consider moving these tests to another module, they're pretty