{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK prune #-}
module Jose.Internal.Parser
( parseJwt
, DecodableJwt (..)
, EncryptedCEK (..)
, Payload (..)
, IV (..)
, Tag (..)
, AAD (..)
, Sig (..)
, SigTarget (..)
)
where
import Control.Applicative
import Data.Aeson (eitherDecodeStrict')
import Data.Attoparsec.ByteString (Parser)
import qualified Data.Attoparsec.ByteString as P
import qualified Data.Attoparsec.ByteString.Char8 as PC
import Data.ByteArray.Encoding (convertFromBase, Base(..))
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Data.Either.Combinators (mapLeft)
import Jose.Jwa
import Jose.Types (JwtError(..), JwtHeader(..), JwsHeader(..), JweHeader(..))
data DecodableJwt
= Unsecured ByteString
| DecodableJws JwsHeader Payload Sig SigTarget
| DecodableJwe JweHeader EncryptedCEK IV Payload Tag AAD
data Tag
= Tag16 ByteString
| Tag24 ByteString
| Tag32 ByteString
data IV
= IV12 ByteString
| IV16 ByteString
newtype Sig = Sig ByteString
newtype SigTarget = SigTarget ByteString
newtype AAD = AAD ByteString
newtype Payload = Payload ByteString
newtype EncryptedCEK = EncryptedCEK ByteString
parseJwt :: ByteString -> Either JwtError DecodableJwt
parseJwt bs = mapLeft (const BadCrypto) $ P.parseOnly jwt bs
jwt :: Parser DecodableJwt
jwt = do
(hdr, raw) <- jwtHeader
case hdr of
UnsecuredH -> Unsecured <$> base64Chunk
JwsH h -> do
payloadB64 <- PC.takeWhile ('.' /=) <* PC.char '.'
payload <- b64Decode payloadB64
s <- sig (jwsAlg h)
pure $ DecodableJws h (Payload payload) s (SigTarget (B.concat [raw, ".", payloadB64]))
JweH h ->
DecodableJwe
<$> pure h
<*> encryptedCEK
<*> iv (jweEnc h)
<*> encryptedPayload
<*> authTag (jweEnc h)
<*> pure (AAD raw)
sig :: JwsAlg -> Parser Sig
sig _ = do
t <- P.takeByteString >>= b64Decode
pure (Sig t)
authTag :: Enc -> Parser Tag
authTag e = do
t <- P.takeByteString >>= b64Decode
case e of
A128GCM -> tag16 t
A192GCM -> tag16 t
A256GCM -> tag16 t
A128CBC_HS256 -> tag16 t
A192CBC_HS384 -> tag24 t
A256CBC_HS512 -> tag32 t
where
badTag = "invalid auth tag"
tag16 t = if B.length t /= 16 then fail badTag else pure (Tag16 t)
tag24 t = if B.length t /= 24 then fail badTag else pure (Tag24 t)
tag32 t = if B.length t /= 32 then fail badTag else pure (Tag32 t)
iv :: Enc -> Parser IV
iv e = do
bs <- base64Chunk
case e of
A128GCM -> iv12 bs
A192GCM -> iv12 bs
A256GCM -> iv12 bs
_ -> iv16 bs
where
iv12 bs = if B.length bs /= 12 then fail "invalid iv" else pure (IV12 bs)
iv16 bs = if B.length bs /= 16 then fail "invalid iv" else pure (IV16 bs)
encryptedCEK :: Parser EncryptedCEK
encryptedCEK = EncryptedCEK <$> base64Chunk
encryptedPayload :: Parser Payload
encryptedPayload = Payload <$> base64Chunk
jwtHeader :: P.Parser (JwtHeader, ByteString)
jwtHeader = do
hdrB64 <- PC.takeWhile ('.' /=) <* PC.char '.'
hdrBytes <- b64Decode hdrB64 :: P.Parser ByteString
hdr <- parseHdr hdrBytes
return (hdr, hdrB64)
where
parseHdr bs = either fail return (eitherDecodeStrict' bs)
base64Chunk :: P.Parser ByteString
base64Chunk = do
bs <- PC.takeWhile ('.' /=) <* PC.char '.'
b64Decode bs
b64Decode :: ByteString -> P.Parser ByteString
b64Decode bs = either (const (fail "Invalid Base64")) return $ convertFromBase Base64URLUnpadded bs