tahoe-ssk-0.2.1.0: src/Tahoe/SDMF/Internal/Capability.hs
-- | Structured representations of SDMF capabilities.
module Tahoe.SDMF.Internal.Capability where
import Prelude hiding (Read)
import Control.Applicative ((<|>))
import Control.Monad (void)
import Crypto.Hash (Digest, SHA256, digestFromByteString)
import Data.Binary (decode)
import qualified Data.ByteArray as ByteArray
import qualified Data.ByteString as B
import qualified Data.ByteString.Base32 as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Void (Void)
import Data.Word (Word16)
import Tahoe.Capability (ConfidentialShowable (..))
import Tahoe.SDMF.Internal.Keys (
Read (readKeyBytes),
StorageIndex (StorageIndex, unStorageIndex),
Write (writeKeyBytes),
deriveReadKey,
deriveStorageIndex,
readKeyBytes,
showBase32,
)
import Text.Megaparsec (
ErrorFancy (ErrorFail),
Parsec,
count,
failure,
fancyFailure,
oneOf,
)
import Text.Megaparsec.Char (char, string)
-- | Any kind of SDMF capability.
data SDMF
= SDMFVerifier Verifier
| SDMFReader Reader
| SDMFWriter Writer
deriving (Eq, Show)
instance ConfidentialShowable SDMF where
confidentiallyShow = dangerRealShow
-- | A verify capability for an SDMF object.
data Verifier = Verifier
{ verifierStorageIndex :: StorageIndex
, verifierVerificationKeyHash :: Digest SHA256
}
deriving (Eq, Show)
instance Ord Verifier where
a <= b = verifierStorageIndex a <= verifierStorageIndex b
instance ConfidentialShowable Verifier where
confidentiallyShow = dangerRealShow . SDMFVerifier
-- | A read capability for an SDMF object.
data Reader = Reader
{ readerReadKey :: Read
, readerVerifier :: Verifier
}
deriving (Eq, Show)
instance Ord Reader where
a <= b = readerVerifier a <= readerVerifier b
instance ConfidentialShowable Reader where
confidentiallyShow = dangerRealShow . SDMFReader
-- | A write capability for an SDMF object.
data Writer = Writer
{ writerWriteKey :: Write
, writerReader :: Reader
}
deriving (Eq, Show)
instance Ord Writer where
a <= b = writerReader a <= writerReader b
instance ConfidentialShowable Writer where
confidentiallyShow = dangerRealShow . SDMFWriter
-- | Diminish a write key to a read key and wrap it in a reader capability.
deriveReader :: Write -> Digest SHA256 -> Maybe Reader
deriveReader w fingerprint = Reader <$> readKey <*> verifier
where
readKey = deriveReadKey w
verifier = flip deriveVerifier fingerprint <$> readKey
-- | Diminish a read key to a verify key and wrap it in a verifier capability.
deriveVerifier :: Read -> Digest SHA256 -> Verifier
deriveVerifier readKey = Verifier storageIndex
where
storageIndex = deriveStorageIndex readKey
type Parser = Parsec Void T.Text
-- | A parser for any kind of SDMF capability type.
pCapability :: Parser SDMF
pCapability = (SDMFVerifier <$> pVerifier) <|> (SDMFReader <$> pReader) <|> (SDMFWriter <$> pWriter)
-- | A parser for an SDMF verifier capability.
pVerifier :: Parser Verifier
pVerifier = uncurry Verifier <$> pPieces "URI:SSK-Verifier:" StorageIndex
-- | A parser for an SDMF reader capability.
pReader :: Parser Reader
pReader = do
(readKey, verificationKeyHash) <- pPieces "URI:SSK-RO:" (decode . LB.fromStrict)
let verifier = deriveVerifier readKey verificationKeyHash
pure $ Reader readKey verifier
-- | A parser for an SDMF writer capability.
pWriter :: Parser Writer
pWriter = do
(writeKey, verificationKeyHash) <- pPieces "URI:SSK:" (decode . LB.fromStrict)
let reader = deriveReader writeKey verificationKeyHash
case Writer writeKey <$> reader of
Nothing -> failure Nothing mempty
Just writer -> pure writer
{- | A parser for two base32-encoded bytestrings with some given prefix,
formatted as they are in the string representation of an SDMF capability.
-}
pPieces ::
-- | The prefix to expect.
T.Text ->
-- | A function to convert the first bytestring to a result value.
(B.ByteString -> a) ->
-- | A parser for the two pieces of the SDMF capability.
Parser (a, Digest SHA256)
pPieces prefix convertSecret = do
void $ string prefix
secret <- convertSecret <$> pBase32 rfc3548Alphabet 128
void $ char ':'
digestBytes <- pBase32 rfc3548Alphabet 256
case digestFromByteString digestBytes of
Nothing -> failure Nothing mempty
Just verificationKeyHash ->
pure (secret, verificationKeyHash)
{- | A parser combinator for an arbitrary byte string of a fixed length,
encoded using base32.
TODO: Avoid duplicating this implementation here and in tahoe-chk.
-}
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"
-- | Show an SDMF capability, including all secret information.
{-# DEPRECATED dangerRealShow "Use the ConfidentialShowable instance" #-}
dangerRealShow :: SDMF -> T.Text
dangerRealShow (SDMFVerifier Verifier{verifierStorageIndex, verifierVerificationKeyHash}) =
T.concat
[ "URI:SSK-Verifier:"
, showBase32 . unStorageIndex $ verifierStorageIndex
, ":"
, showBase32 . ByteArray.convert $ verifierVerificationKeyHash
]
dangerRealShow (SDMFReader Reader{readerReadKey, readerVerifier}) =
T.concat
[ "URI:SSK-RO:"
, showBase32 . ByteArray.convert . readKeyBytes $ readerReadKey
, ":"
, showBase32 . ByteArray.convert . verifierVerificationKeyHash $ readerVerifier
]
dangerRealShow (SDMFWriter Writer{writerWriteKey, writerReader}) =
T.concat
[ "URI:SSK:"
, showBase32 . ByteArray.convert . writeKeyBytes $ writerWriteKey
, ":"
, showBase32 . ByteArray.convert . verifierVerificationKeyHash . readerVerifier $ writerReader
]