Skip to content
Snippets Groups Projects
Commit 7bf97105 authored by Jean-Paul Calderone's avatar Jean-Paul Calderone
Browse files

expose random IV generation in the public interface

callers of the encode interface are now required to pass in the correct iv for
the ciphertext being encoded
parent ec91a9dd
No related branches found
No related tags found
1 merge request!10expose random IV generation in the public interface
......@@ -24,6 +24,7 @@ import Tahoe.SDMF.Internal.Encoding (
import Tahoe.SDMF.Internal.Encrypting (
decrypt,
encrypt,
randomIV,
)
import Tahoe.SDMF.Internal.Share (
Share (..),
......
......@@ -7,9 +7,8 @@ module Tahoe.SDMF.Internal.Encoding where
import Control.Monad (when)
import Control.Monad.IO.Class (MonadIO (liftIO))
import Crypto.Cipher.Types (BlockCipher (blockSize), IV, makeIV)
import Crypto.Hash (digestFromByteString)
import Crypto.Random (MonadRandom (getRandomBytes))
import Crypto.Random (MonadRandom)
import Data.Bifunctor (Bifunctor (bimap))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
......@@ -23,10 +22,6 @@ import Tahoe.SDMF.Internal.Converting (from, tryInto)
import qualified Tahoe.SDMF.Internal.Keys as Keys
import Tahoe.SDMF.Internal.Share (HashChain (HashChain), Share (..))
-- | Randomly generate a new IV suitable for use with some BlockCipher.
randomIV :: forall c m. (BlockCipher c, MonadRandom m) => m (Maybe (IV c))
randomIV = (makeIV :: B.ByteString -> Maybe (IV c)) <$> getRandomBytes (blockSize (undefined :: c))
{- | Given a pre-determined key pair and sequence number, encode some
ciphertext into a collection of SDMF shares.
......@@ -34,8 +29,8 @@ randomIV = (makeIV :: B.ByteString -> Maybe (IV c)) <$> getRandomBytes (blockSiz
Thus they cannot be re-used for "different" data. Any shares created with a
given key pair are part of the same logical data object.
-}
encode :: (MonadFail m, MonadIO m, MonadRandom m) => Keys.KeyPair -> Word64 -> Word16 -> Word16 -> LB.ByteString -> m ([Share], Writer)
encode keypair shareSequenceNumber required total ciphertext = do
encode :: (MonadFail m, MonadIO m, MonadRandom m) => Keys.KeyPair -> Keys.SDMF_IV -> Word64 -> Word16 -> Word16 -> LB.ByteString -> m ([Share], Writer)
encode keypair iv shareSequenceNumber required total ciphertext = do
-- Make sure the encoding parameters fit into a Word8
requiredAsWord8 <- tryInto @Word8 ("must have 0 < required < 255 but required == " <> show required) required
totalAsWord8 <- tryInto @Word8 ("must have 0 < total < 256 but total == " <> show total) total
......@@ -46,8 +41,6 @@ encode keypair shareSequenceNumber required total ciphertext = do
-- They look okay, we can proceed.
blocks <- liftIO $ fmap LB.fromStrict <$> zfec (from required) (from total) paddedCiphertext
(Just iv) <- randomIV
-- We know the length won't be negative (doesn't make sense) and we
-- know all positive values fit into a Word64 so we can do this
-- conversion safely. But if it needs to fail for some reason, it
......@@ -63,7 +56,7 @@ encode keypair shareSequenceNumber required total ciphertext = do
flip $
makeShare
shareSequenceNumber
(Keys.SDMF_IV iv)
iv
requiredAsWord8
totalAsWord8
dataLength
......
-- | Implement the encryption scheme used by SDMF.
module Tahoe.SDMF.Internal.Encrypting where
import Crypto.Cipher.Types (ctrCombine, nullIV)
import Crypto.Cipher.AES (AES128)
import Crypto.Cipher.Types (BlockCipher (blockSize), ctrCombine, makeIV, nullIV)
import Crypto.Random (MonadRandom (getRandomBytes))
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB
import qualified Tahoe.SDMF.Internal.Keys as Keys
-- | Randomly generate a new IV suitable for use with the block cipher used by SDMF.
randomIV :: MonadRandom m => m (Maybe Keys.SDMF_IV)
randomIV = (fmap Keys.SDMF_IV . makeIV :: B.ByteString -> Maybe Keys.SDMF_IV) <$> getRandomBytes (blockSize (undefined :: AES128))
{- | Encrypt plaintext bytes according to the scheme used for SDMF share
construction.
-}
encrypt :: Keys.Data -> LB.ByteString -> LB.ByteString
encrypt Keys.Data{unData} = LB.fromStrict . ctrCombine unData nullIV . LB.toStrict
encrypt :: Keys.KeyPair -> Keys.SDMF_IV -> LB.ByteString -> LB.ByteString
encrypt keypair iv = encryptWithDataKey dataKey
where
signatureKey = Keys.toSignatureKey keypair
(Just writeKey) = Keys.deriveWriteKey signatureKey
(Just readKey) = Keys.deriveReadKey writeKey
(Just dataKey) = Keys.deriveDataKey iv readKey
{- | Decrypt ciphertext bytes according to the scheme used for SDMF share
construction.
-}
decrypt :: Keys.Data -> LB.ByteString -> LB.ByteString
decrypt = encrypt
decrypt :: Keys.Read -> Keys.SDMF_IV -> LB.ByteString -> LB.ByteString
decrypt readKey iv = decryptWithDataKey dataKey
where
(Just dataKey) = Keys.deriveDataKey iv readKey
{- | Encrypt plaintext bytes according to the scheme used for SDMF share
construction using a pre-computed data encryption key.
-}
encryptWithDataKey :: Keys.Data -> LB.ByteString -> LB.ByteString
encryptWithDataKey Keys.Data{unData} = LB.fromStrict . ctrCombine unData nullIV . LB.toStrict
{- | Decrypt ciphertext bytes according to the scheme used for SDMF share
construction using a pre-computed data encryption key.
-}
decryptWithDataKey :: Keys.Data -> LB.ByteString -> LB.ByteString
decryptWithDataKey = encryptWithDataKey
......@@ -22,7 +22,7 @@ import Data.ByteString.Base32 (decodeBase32Unpadded, encodeBase32Unpadded)
import qualified Data.ByteString.Lazy as LB
import Data.Either (rights)
import qualified Data.Text as T
import Generators (capabilities, encodingParameters, genRSAKeys, shareHashChains, shares)
import Generators (capabilities, encodingParameters, genRSAKeys, ivLength, shareHashChains, shares)
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
import System.IO (hSetEncoding, stderr, stdout, utf8)
......@@ -175,11 +175,13 @@ tests =
, testProperty "Ciphertext round-trips through encode . decode" $
property $ do
keypair <- forAll genRSAKeys
ivBytes <- forAll $ Gen.bytes (Range.singleton ivLength)
let Just iv = Keys.SDMF_IV <$> makeIV ivBytes
ciphertext <- forAll $ LB.fromStrict <$> Gen.bytes (Range.exponential 1 1024)
sequenceNumber <- forAll $ Gen.integral Range.exponentialBounded
(required, total) <- forAll encodingParameters
(shares', Tahoe.SDMF.Writer{Tahoe.SDMF.writerReader}) <- liftIO $ Tahoe.SDMF.encode keypair sequenceNumber required total ciphertext
(shares', Tahoe.SDMF.Writer{Tahoe.SDMF.writerReader}) <- liftIO $ Tahoe.SDMF.encode keypair iv sequenceNumber required total ciphertext
annotateShow shares'
......@@ -190,12 +192,11 @@ tests =
do
keypair <- forAll genRSAKeys
(Just iv) <- fmap Keys.SDMF_IV <$> (makeIV <$> forAll (Gen.bytes (Range.singleton 16)))
let (Just dataKey) = do
let (Just readKey) = do
writeKey <- Keys.deriveWriteKey (Keys.toSignatureKey keypair)
readKey <- Keys.deriveReadKey writeKey
Keys.deriveDataKey iv readKey
Keys.deriveReadKey writeKey
plaintext <- forAll $ LB.fromStrict <$> Gen.bytes (Range.exponential 1 1024)
tripping plaintext (Tahoe.SDMF.encrypt dataKey) (Just . Tahoe.SDMF.decrypt dataKey)
tripping plaintext (Tahoe.SDMF.encrypt keypair iv) (Just . Tahoe.SDMF.decrypt readKey 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)
......@@ -213,7 +214,7 @@ tests =
expectedPlaintext = "abcdefghijklmnopqrstuvwxyzZYXWVUTSRQPONMLKJIJHGRFCBA1357"
(Just dataKey) = Keys.deriveDataKey (Tahoe.SDMF.shareIV s0) readerReadKey
recoveredPlaintext = Tahoe.SDMF.decrypt dataKey ciphertext
recoveredPlaintext = Tahoe.SDMF.decrypt readerReadKey (Tahoe.SDMF.shareIV s0) ciphertext
assertEqual "read key: expected /= derived" expectedReadKey readerReadKey
assertEqual "data key: expected /= derived" expectedDataKey dataKey
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment