From a501ffb0389ce389c92d276f0478eabdcbde6448 Mon Sep 17 00:00:00 2001 From: Jean-Paul Calderone <exarkun@twistedmatrix.com> Date: Sat, 13 May 2023 09:01:30 -0400 Subject: [PATCH] Most of an interoperability test with Tahoe-LAFS data It doesn't have an assert but it prints the wrong output --- src/Tahoe/SDMF/Internal/Encoding.hs | 2 +- src/Tahoe/SDMF/Internal/Keys.hs | 14 ++++++++-- test/Spec.hs | 43 +++++++++++++++++++++++------ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/src/Tahoe/SDMF/Internal/Encoding.hs b/src/Tahoe/SDMF/Internal/Encoding.hs index 6414741..f7c5c89 100644 --- a/src/Tahoe/SDMF/Internal/Encoding.hs +++ b/src/Tahoe/SDMF/Internal/Encoding.hs @@ -76,7 +76,7 @@ makeShare shareSequenceNumber shareIV shareRequiredShares shareTotalShares share decode :: (MonadFail m, MonadIO m) => Reader -> [(Word16, Share)] -> m LB.ByteString decode _ [] = fail "Cannot decode with no shares" decode _ s@((_, Share{shareRequiredShares, shareTotalShares, shareSegmentSize}) : shares) - | length shares < fromIntegral shareRequiredShares = fail $ "got " <> show (length shares) <> " shares, required " <> show shareRequiredShares + | length s < fromIntegral shareRequiredShares = fail $ "got " <> show (length shares) <> " shares, required " <> show shareRequiredShares | otherwise = do ciphertext <- liftIO $ zunfec (fromIntegral shareRequiredShares) (fromIntegral shareTotalShares) (take (fromIntegral shareRequiredShares) blocks) pure . LB.take (fromIntegral shareSegmentSize) . LB.fromStrict $ ciphertext diff --git a/src/Tahoe/SDMF/Internal/Keys.hs b/src/Tahoe/SDMF/Internal/Keys.hs index f82a316..2617bb4 100644 --- a/src/Tahoe/SDMF/Internal/Keys.hs +++ b/src/Tahoe/SDMF/Internal/Keys.hs @@ -8,14 +8,17 @@ import Prelude hiding (Read) import Control.Monad (when) import Crypto.Cipher.AES (AES128) -import Crypto.Cipher.Types (BlockCipher (ctrCombine), Cipher (cipherInit, cipherKeySize), IV, KeySizeSpecifier (KeySizeFixed), nullIV) -import Crypto.Error (maybeCryptoError) +import Crypto.Cipher.Types (BlockCipher (ctrCombine), Cipher (cipherInit, cipherKeySize), IV, KeySizeSpecifier (KeySizeFixed), makeIV, nullIV) +import Crypto.Error (CryptoFailable (CryptoPassed), maybeCryptoError) import qualified Crypto.PubKey.RSA as RSA import Crypto.Random (MonadRandom) import Data.ASN1.BinaryEncoding (DER (DER)) import Data.ASN1.Encoding (ASN1Encoding (encodeASN1), decodeASN1') import Data.ASN1.Types (ASN1 (End, IntVal, Null, OID, OctetString, Start), ASN1ConstructionType (Sequence), ASN1Object (fromASN1, toASN1)) import Data.Bifunctor (Bifunctor (first)) +import Data.Binary (Binary (get, put)) +import Data.Binary.Get (getByteString) +import Data.Binary.Put (putByteString) import qualified Data.ByteArray as ByteArray import qualified Data.ByteString as B import Data.ByteString.Base32 (encodeBase32Unpadded) @@ -43,6 +46,13 @@ newtype Signature = Signature {unSignature :: RSA.PrivateKey} data Write = Write {unWrite :: AES128, writeKeyBytes :: ByteArray.ScrubbedBytes} +instance Binary Write where + put = putByteString . ByteArray.convert . writeKeyBytes + get = do + writeKeyBytes <- ByteArray.convert <$> getByteString 16 + let (CryptoPassed unWrite) = cipherInit writeKeyBytes + pure Write{..} + instance Show Write where show (Write _ bs) = T.unpack $ T.concat ["<WriteKey ", encodeBase32Unpadded (ByteArray.convert bs), ">"] diff --git a/test/Spec.hs b/test/Spec.hs index df71c87..7f2bd9f 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -11,6 +11,7 @@ import Hedgehog ( tripping, ) +import Control.Monad (when) import Control.Monad.IO.Class (liftIO) import Crypto.Cipher.Types (makeIV) import Data.ASN1.BinaryEncoding (DER (DER)) @@ -19,7 +20,7 @@ import qualified Data.Binary as Binary import Data.Binary.Get (ByteOffset) import qualified Data.ByteArray as ByteArray import qualified Data.ByteString as B -import Data.ByteString.Base32 (encodeBase32Unpadded) +import Data.ByteString.Base32 (decodeBase32Unpadded, encodeBase32Unpadded) import qualified Data.ByteString.Lazy as LB import qualified Data.Text as T import Generators (encodingParameters, genRSAKeys, shareHashChains, shares) @@ -171,8 +172,33 @@ tests = Keys.deriveDataKey iv readKey plaintext <- forAll $ LB.fromStrict <$> Gen.bytes (Range.exponential 1 1024) tripping plaintext (Tahoe.SDMF.encrypt dataKey iv) (Just . Tahoe.SDMF.decrypt dataKey iv) + , testCase "Recover plaintext from a known-correct slot" $ do + s0 <- liftIO $ Binary.decode <$> (LB.readFile "test/data/3of10.0" >>= readShareFromBucket) + s6 <- liftIO $ Binary.decode <$> (LB.readFile "test/data/3of10.6" >>= readShareFromBucket) + s9 <- liftIO $ Binary.decode <$> (LB.readFile "test/data/3of10.9" >>= readShareFromBucket) + + let (Right writeKey) = Binary.decode . LB.fromStrict <$> decodeBase32Unpadded "vdv6pcqkblsguvkagrblr3gopu" + (Just readerReadKey) = Keys.deriveReadKey writeKey + readerVerificationKeyHash = "junk" + reader = Tahoe.SDMF.Reader{..} + ciphertext <- Tahoe.SDMF.decode reader [(0, s0), (6, s6), (9, s9)] + let (Just dataKey) = Keys.deriveDataKey (Tahoe.SDMF.shareIV s0) readerReadKey + plaintext = Tahoe.SDMF.decrypt dataKey (Tahoe.SDMF.shareIV s0) ciphertext + + print plaintext + pure () ] +readShareFromBucket :: MonadFail m => LB.ByteString -> m LB.ByteString +readShareFromBucket bucket = + let withoutPrefix = LB.drop (32 + 20 + 32 + 8 + 8 + 368) bucket + dataSize = LB.length withoutPrefix - 4 + shareData = LB.take dataSize withoutPrefix + suffix = LB.drop dataSize withoutPrefix + in do + when (suffix /= "\0\0\0\0") (fail "Cannot account for extra leases") + pure shareData + {- | Load a known-correct SDMF bucket and assert that bytes in the slot it contains deserializes to a Share and then serializes back to the same bytes @@ -187,13 +213,14 @@ knownCorrectRoundTrip n = do -- having to parse the prefix, we assert that the suffix is a -- predictable size. bucket <- LB.readFile ("test/data/3of10." <> show n) - let withoutPrefix = LB.drop (32 + 20 + 32 + 8 + 8 + 368) bucket - dataSize = LB.length withoutPrefix - 4 - shareData = LB.take dataSize withoutPrefix - suffix = LB.drop dataSize withoutPrefix - - -- Our assumption about the data we're working on... - assertEqual "Cannot account for extra leases" suffix "\0\0\0\0" + shareData <- readShareFromBucket bucket + -- let withoutPrefix = LB.drop (32 + 20 + 32 + 8 + 8 + 368) bucket + -- dataSize = LB.length withoutPrefix - 4 + -- shareData = LB.take dataSize withoutPrefix + -- suffix = LB.drop dataSize withoutPrefix + + -- -- Our assumption about the data we're working on... + -- assertEqual "Cannot account for extra leases" suffix "\0\0\0\0" let decoded = decode' shareData let encoded = (Binary.encode :: Tahoe.SDMF.Share -> LB.ByteString) <$> decoded -- GitLab