packages feed

tahoe-chk-0.2.0.0: src/Tahoe/CHK/Capability.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

module Tahoe.CHK.Capability (
    CHK (..),
    Reader,
    readKey,
    verifier,
    Verifier,
    storageIndex,
    fingerprint,
    required,
    total,
    size,
    makeReader,
    pCapability,
    pVerifier,
    pReader,
    dangerRealShow,
) where

import Control.Lens (view)
import Control.Lens.TH (makeLenses)
import Crypto.Cipher.AES (AES128)
import Crypto.Cipher.Types (Cipher (cipherInit))
import Crypto.Error (maybeCryptoError)
import Data.ByteArray (convert)
import qualified Data.ByteString as B
import qualified Data.ByteString.Base32 as B
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.TreeDiff.Class (ToExpr (..))
import Data.Void (Void)
import Data.Word (Word16, Word64)
import GHC.Generics (Generic)
import Tahoe.CHK.Cipher (Key (..))
import Tahoe.CHK.Crypto (storageIndexHash)
import qualified Tahoe.CHK.Parsing
import Text.Megaparsec (ErrorFancy (ErrorFail), Parsec, count, fancyFailure, oneOf, try, (<|>))
import Text.Megaparsec.Char (char, string)
import Text.Megaparsec.Char.Lexer (decimal)

{- | Define a type in which we will perform parsing.  There is no custom error
 data (Void) and we are parsing T.Text.
-}
type Parser = Parsec Void T.Text

{- | The maximum number of shares it is possible for CHK-encoded data to be
 divided in to.
-}
maxShares :: Word16
maxShares = 256

-- | The maximum size of the application data represented by a set of shares.
maxDataSize :: Integer
maxDataSize = fromIntegral (maxBound :: Word64)

{- | Represent a CHK "verify" capability.  This capability type can be used to
 verify the existence and validity (bit-level) of shares for the associated
 piece of plaintext.

 It can also be used to repair unhealthy data (I think?)
-}
data Verifier = Verifier
    { -- | The storage index of a verify capability is used as the key into the
      -- content-addressable storage system that is a storage server.  It can be
      -- used to ask storage servers for "shares" (ciphertext plus some
      -- metadata) to download.
      _storageIndex :: B.ByteString
    , -- | The fingerprint (aka "UEB hash" aka "URI extension block hash") is a
      -- cryptographic hash that covers the URI extension block at the end of a
      -- CHK share.  The URI extension block itself contains various other
      -- cryptographic hashes.  Altogether this allows for integrity checking so
      -- shares downloaded from storage servers can be checked for validity (ie,
      -- that they are the same as what was uploaded) before they are processed.
      _fingerprint :: B.ByteString
    , -- | The number of shares required to ZFEC decode the contents of the
      -- shares.  ZFEC calls this *K*.  It must be that 1 <= required <= 256 and
      -- required <= total.  ZFEC is not defined outside of these bounds.
      _required :: Word16
    , -- | The total number of shares produced by ZFEC encoding.  ZFEC calls
      -- this *n*.  It must be that 1 <= total <= 256 and required <= total.
      _total :: Word16
    , -- | The size (in bytes) of the plaintext encoded in the shares.  It must
      -- be that size >= 0 and in practice it is usually true that size >= 56.
      _size :: Integer
    }
    deriving (Ord, Eq, Generic, ToExpr)

$(makeLenses ''Verifier)

{- | Replace most of the tail of a string with a short placeholder.  If the
 string is not much longer than `n` then the result might not actually be
 shorter.
-}
shorten :: Int -> T.Text -> T.Text
shorten n = (<> "...") . T.take n

-- | Show a value as Text.
showT :: Show s => s -> T.Text
showT = T.pack . show

-- | Show a ByteString using a base32-encoded representation.
showBase32 :: B.ByteString -> T.Text
showBase32 = T.toLower . B.encodeBase32Unpadded

-- | A version of bounded specialized to parsing text.
bounded :: (Ord n, Integral n) => n -> n -> Parser n
bounded = Tahoe.CHK.Parsing.bounded decimal

instance Show Verifier where
    show v =
        T.unpack $
            T.intercalate
                ":"
                [ "URI"
                , "CHK-Verifier"
                , shorten 4 . showBase32 $ view storageIndex v
                , shorten 4 . showBase32 $ view fingerprint v
                , showT $ view required v
                , showT $ view total v
                , showT $ view size v
                ]

{- | Represent a CHK "read" capability.  This capability type can be diminished
 to a verify capability so it confers all of the abilities of a verify
 capability.  It can also be used to decrypt shares to reconstruct the
 original plaintext.  See makeReader for a safe constructor that correctly
 derives the verify capability.
-}
data Reader = Reader
    { -- | The read key of a read capability is used as the symmetric encryption
      -- key to turn the original plaintext into ciphertext and back again.  The
      -- read key is also used to derive the verify key for the verify
      -- capability.  See ``storageIndexHash``.
      _readKey :: Key AES128
    , -- | The verify capability for this read capability.
      _verifier :: Verifier
    }

$(makeLenses ''Reader)

-- AESKey128 has no Eq or Ord instances so derive these for Reader manually.
-- We do include the AESKey128 in our comparison by encoding it to bytes
-- first.
instance Eq Reader where
    left == right = readerKey left == readerKey right

instance Ord Reader where
    compare left right = compare (readerKey left) (readerKey right)

instance ToExpr Reader where
    toExpr = toExpr . readerKey

{- | Give it a Show instance that elides the sensitive material.  This makes
 it easier to compose with other types and we can still learn a lot of
 useful things about a capability without being able to see the literal
 secret key.
-}
instance Show Reader where
    show reader =
        T.unpack $
            T.intercalate
                ":"
                [ "URI"
                , "CHK"
                , shorten 4 . showBase32 . convert . keyBytes $ view readKey reader
                , shorten 4 . showBase32 $ view (verifier . fingerprint) reader
                , showT $ view (verifier . required) reader
                , showT $ view (verifier . total) reader
                , showT $ view (verifier . size) reader
                ]

-- Construct a key with Eq and Ord instances for the Reader Eq and Ord
-- instances.
readerKey :: Reader -> (B.ByteString, Verifier)
readerKey r = (convert $ view readKey r, view verifier r)

{- | A "Content-Hash-Key" (CHK) capability is small value that can be used to
 perform some operation on a (usually) larger value that may be stored
 somewhere else.  There are two forms of CHK capabilities: verify and read.
 See *Verifier* and *Reader* for details.
-}
data CHK = CHKVerifier Verifier | CHKReader Reader deriving (Ord, Eq)

{- | Serialize a CHK capability to text.  This operation is "dangerous" in
 that it will serialize the encryption key of a read capability into the
 text.  Since the encryption key is necessary and (practically) sufficient
 to recover the original plaintext associated with the capability, it must
 be handled carefully to avoid unintentional disclosure.  Serializing the
 key to a string is a good way to accidentally disclose it!  Be warned.

 The text is in the canonical form, originally used by the Python
 implementation of Tahoe-LAFS.
-}
dangerRealShow :: CHK -> T.Text
dangerRealShow (CHKVerifier v) =
    T.intercalate
        ":"
        [ "URI"
        , "CHK-Verifier"
        , showBase32 $ view storageIndex v
        , showBase32 $ view fingerprint v
        , showT $ view required v
        , showT $ view total v
        , showT $ view size v
        ]
dangerRealShow (CHKReader r) =
    T.intercalate
        ":"
        [ "URI"
        , "CHK"
        , showBase32 . convert $ view readKey r
        , showBase32 $ view (verifier . fingerprint) r
        , showT $ view (verifier . required) r
        , showT $ view (verifier . total) r
        , showT $ view (verifier . size) r
        ]

{- | A parser combinator for parsing either a verify or read CHK capability
 from the canonical format.  This is the moral inverse of dangerRealShow.
-}
pCapability :: Parser CHK
pCapability = try (CHKVerifier <$> pVerifier) <|> (CHKReader <$> pReader)

-- | A parser combinator for parsing a CHK verify capability.
pVerifier :: Parser Verifier
pVerifier =
    Verifier
        <$> (string "URI:CHK-Verifier:" *> pBase32 rfc3548Alphabet 128)
        <* char ':'
        <*> pBase32 rfc3548Alphabet 256
        <* char ':'
        <*> bounded 1 maxShares
        <* char ':'
        <*> bounded 1 maxShares
        <* char ':'
        <*> bounded 1 maxDataSize

-- | A parser combinator for parsing a CHK read capability.
pReader :: Parser Reader
pReader =
    makeReader
        <$> ( string "URI:CHK:"
                *> pBase32 rfc3548Alphabet 128
                >>= maybe (fancyFailure . Set.singleton . ErrorFail . T.unpack $ "Failed to build AESKey128 from CHK read key bytes") pure . maybeCryptoError . cipherInit
            )
        <* char ':'
        <*> pBase32 rfc3548Alphabet 256
        <* char ':'
        <*> bounded 1 256
        <* char ':'
        <*> bounded 1 256
        <* char ':'
        <*> bounded 1 maxDataSize

{- | Construct a CHK read capability from its components.  This includes the
 correct derivation of the corresponding CHK verify capability.
-}
makeReader :: Key AES128 -> B.ByteString -> Word16 -> Word16 -> Integer -> Reader
makeReader readKey' fingerprint' required' total' size' =
    Reader readKey' (deriveVerifier readKey' fingerprint' required' total' size')

{- | Given all of the fields of a CHK read capability, derive and return the
 corresponding CHK verify capability.
-}
deriveVerifier ::
    -- | The read key
    Key AES128 ->
    -- | The fingerprint
    B.ByteString ->
    -- | The required number of shares
    Word16 ->
    -- | The total number of shares
    Word16 ->
    -- | The plaintext size
    Integer ->
    Verifier
deriveVerifier = Verifier . storageIndexHash

{- | A parser combinator for an arbitrary byte string of a fixed length,
 encoded using base32.
-}
pBase32 ::
    -- | The alphabet to use.  For example, *rfc3548Alphabet*.
    [Char] ->
    -- | The number of bits in the encoded byte string.
    Word16 ->
    -- | A parser for the byte string.  Strings that are not valid base32 will
    -- be rejected.  Strings that are the wrong length are *not necessarily*
    -- currently rejected!  Please fix that, somebody.
    Parser B.ByteString
pBase32 alpha bits = do
    b32Text <- pBase32Text
    either (fancyFailure . Set.singleton . ErrorFail . T.unpack) pure (decodeBase32Text b32Text)
  where
    decodeBase32Text = B.decodeBase32Unpadded . T.encodeUtf8
    pBase32Text = T.snoc <$> stem <*> trailer

    -- Determine how many full characters to expect along with how many bits
    -- are left to expect encoded in the final character.
    (full, extra) = bits `divMod` 5

    -- Match the base32 characters that represent the full 5 bits
    -- possible.  fromIntegral is okay here because `full` is only a
    -- Word16 and will definitely fit safely into the Int count wants.
    stem :: Parser T.Text
    stem = T.pack <$> count (fromIntegral full) (oneOf alpha)

    -- Match the final character that represents fewer than 5 bits.
    trailer :: Parser Char
    trailer = oneOf $ trailingChars alpha extra

    -- XXX The real trailing character set is smaller than this.  This
    -- parser will let through invalid characters that result in giving us
    -- possibly too many bits.
    trailingChars :: [Char] -> Word16 -> [Char]
    trailingChars alpha' _ = alpha'

{- | The RFC3548 standard alphabet used by Gnutella, Content-Addressable Web,
 THEX, Bitzi, Web-Calculus...
-}
rfc3548Alphabet :: [Char]
rfc3548Alphabet = "abcdefghijklmnopqrstuvwxyz234567"