{-# LANGUAGE OverloadedStrings #-}
module SDJWT.Internal.Digest
( computeDigest
, computeDigestText
, verifyDigest
, parseHashAlgorithm
, defaultHashAlgorithm
, hashAlgorithmToText
, extractDigestsFromValue
, extractDigestStringsFromSDArray
) where
import SDJWT.Internal.Types (HashAlgorithm(..), Digest(..), EncodedDisclosure(..), SDJWTError(..))
import SDJWT.Internal.Utils (hashToBytes, base64urlEncode, constantTimeEq, textToByteString)
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Vector as V
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Maybe (mapMaybe)
import Control.Monad (mapM)
defaultHashAlgorithm :: HashAlgorithm
defaultHashAlgorithm :: HashAlgorithm
defaultHashAlgorithm = HashAlgorithm
SHA256
hashAlgorithmToText :: HashAlgorithm -> T.Text
hashAlgorithmToText :: HashAlgorithm -> Text
hashAlgorithmToText HashAlgorithm
SHA256 = Text
"sha-256"
hashAlgorithmToText HashAlgorithm
SHA384 = Text
"sha-384"
hashAlgorithmToText HashAlgorithm
SHA512 = Text
"sha-512"
parseHashAlgorithm :: T.Text -> Maybe HashAlgorithm
parseHashAlgorithm :: Text -> Maybe HashAlgorithm
parseHashAlgorithm Text
"sha-256" = HashAlgorithm -> Maybe HashAlgorithm
forall a. a -> Maybe a
Just HashAlgorithm
SHA256
parseHashAlgorithm Text
"sha-384" = HashAlgorithm -> Maybe HashAlgorithm
forall a. a -> Maybe a
Just HashAlgorithm
SHA384
parseHashAlgorithm Text
"sha-512" = HashAlgorithm -> Maybe HashAlgorithm
forall a. a -> Maybe a
Just HashAlgorithm
SHA512
parseHashAlgorithm Text
_ = Maybe HashAlgorithm
forall a. Maybe a
Nothing
computeDigest :: HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest :: HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
alg (EncodedDisclosure Text
encoded) =
let
disclosureBytes :: ByteString
disclosureBytes = Text -> ByteString
TE.encodeUtf8 Text
encoded
hashBytes :: ByteString
hashBytes = HashAlgorithm -> ByteString -> ByteString
hashToBytes HashAlgorithm
alg ByteString
disclosureBytes
digestText :: Text
digestText = ByteString -> Text
base64urlEncode ByteString
hashBytes
in
Text -> Digest
Digest Text
digestText
computeDigestText :: HashAlgorithm -> EncodedDisclosure -> T.Text
computeDigestText :: HashAlgorithm -> EncodedDisclosure -> Text
computeDigestText HashAlgorithm
alg = Digest -> Text
unDigest (Digest -> Text)
-> (EncodedDisclosure -> Digest) -> EncodedDisclosure -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
alg
verifyDigest :: HashAlgorithm -> Digest -> EncodedDisclosure -> Bool
verifyDigest :: HashAlgorithm -> Digest -> EncodedDisclosure -> Bool
verifyDigest HashAlgorithm
alg Digest
expectedDigest EncodedDisclosure
disclosure =
let
computedDigest :: Digest
computedDigest = HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
alg EncodedDisclosure
disclosure
expectedBytes :: ByteString
expectedBytes = Text -> ByteString
textToByteString (Digest -> Text
unDigest Digest
expectedDigest)
computedBytes :: ByteString
computedBytes = Text -> ByteString
textToByteString (Digest -> Text
unDigest Digest
computedDigest)
in
ByteString -> ByteString -> Bool
constantTimeEq ByteString
expectedBytes ByteString
computedBytes
extractDigestsFromValue :: Aeson.Value -> Either SDJWTError [Digest]
(Aeson.Object Object
obj) = do
[Digest]
topLevelDigests <- case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup Key
"_sd" Object
obj of
Just (Aeson.Array Array
arr) ->
(Value -> Either SDJWTError Digest)
-> [Value] -> Either SDJWTError [Digest]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\Value
v -> case Value
v of
Aeson.String Text
s -> Digest -> Either SDJWTError Digest
forall a b. b -> Either a b
Right (Text -> Digest
Digest Text
s)
Value
_ -> SDJWTError -> Either SDJWTError Digest
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError Digest)
-> SDJWTError -> Either SDJWTError Digest
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDigest Text
"_sd array must contain only string digests (RFC 9901 Section 4.2.4.1)"
) (Array -> [Value]
forall a. Vector a -> [a]
V.toList Array
arr)
Maybe Value
_ -> [Digest] -> Either SDJWTError [Digest]
forall a b. b -> Either a b
Right []
[[Digest]]
nestedDigests <- ((Key, Value) -> Either SDJWTError [Digest])
-> [(Key, Value)] -> Either SDJWTError [[Digest]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (Value -> Either SDJWTError [Digest]
extractDigestsFromValue (Value -> Either SDJWTError [Digest])
-> ((Key, Value) -> Value)
-> (Key, Value)
-> Either SDJWTError [Digest]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Key, Value) -> Value
forall a b. (a, b) -> b
snd) (Object -> [(Key, Value)]
forall v. KeyMap v -> [(Key, v)]
KeyMap.toList Object
obj)
[Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Digest] -> Either SDJWTError [Digest])
-> [Digest] -> Either SDJWTError [Digest]
forall a b. (a -> b) -> a -> b
$ [Digest]
topLevelDigests [Digest] -> [Digest] -> [Digest]
forall a. [a] -> [a] -> [a]
++ [[Digest]] -> [Digest]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Digest]]
nestedDigests
extractDigestsFromValue (Aeson.Array Array
arr) = do
let elements :: [Value]
elements = Array -> [Value]
forall a. Vector a -> [a]
V.toList Array
arr
[[Digest]]
results <- (Value -> Either SDJWTError [Digest])
-> [Value] -> Either SDJWTError [[Digest]]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
forall (m :: * -> *) a b. Monad m => (a -> m b) -> [a] -> m [b]
mapM (\Value
el -> case Value
el of
Aeson.Object Object
obj ->
case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
"...") Object
obj of
Just (Aeson.String Text
digest) -> do
if Object -> Int
forall v. KeyMap v -> Int
KeyMap.size Object
obj Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1
then [Digest] -> Either SDJWTError [Digest]
forall a b. b -> Either a b
Right [Text -> Digest
Digest Text
digest]
else SDJWTError -> Either SDJWTError [Digest]
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError [Digest])
-> SDJWTError -> Either SDJWTError [Digest]
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDigest Text
"Ellipsis object must contain only the \"...\" key (RFC 9901 Section 4.2.4.2)"
Maybe Value
_ -> Value -> Either SDJWTError [Digest]
extractDigestsFromValue Value
el
Value
_ -> Value -> Either SDJWTError [Digest]
extractDigestsFromValue Value
el
) [Value]
elements
[Digest] -> Either SDJWTError [Digest]
forall a. a -> Either SDJWTError a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Digest] -> Either SDJWTError [Digest])
-> [Digest] -> Either SDJWTError [Digest]
forall a b. (a -> b) -> a -> b
$ [[Digest]] -> [Digest]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [[Digest]]
results
extractDigestsFromValue Value
_ = [Digest] -> Either SDJWTError [Digest]
forall a b. b -> Either a b
Right []
extractDigestStringsFromSDArray :: Aeson.Object -> [T.Text]
Object
obj =
case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup Key
"_sd" Object
obj of
Just (Aeson.Array Array
arr) ->
(Value -> Maybe Text) -> [Value] -> [Text]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\Value
v -> case Value
v of
Aeson.String Text
s -> Text -> Maybe Text
forall a. a -> Maybe a
Just Text
s
Value
_ -> Maybe Text
forall a. Maybe a
Nothing
) (Array -> [Value]
forall a. Vector a -> [a]
V.toList Array
arr)
Maybe Value
_ -> []