{-# LANGUAGE OverloadedStrings #-}
module SDJWT.Internal.Issuance
(
createSDJWT
, createSDJWTWithDecoys
, addDecoyDigest
, buildSDJWTPayload
, addHolderKeyToClaims
) where
import SDJWT.Internal.Types (HashAlgorithm(..), Salt(..), Digest(..), EncodedDisclosure(..), SDJWTPayload(..), SDJWT(..), SDJWTError(..))
import SDJWT.Internal.Utils (generateSalt, hashToBytes, base64urlEncode, splitJSONPointer, unescapeJSONPointer, groupPathsByFirstSegment)
import SDJWT.Internal.Digest (computeDigest, hashAlgorithmToText)
import SDJWT.Internal.Disclosure (createObjectDisclosure, createArrayDisclosure)
import SDJWT.Internal.JWT (signJWTWithHeaders, JWKLike)
import SDJWT.Internal.Monad (SDJWTIO, runSDJWTIO, partitionAndHandle)
import SDJWT.Internal.Issuance.Nested (processNestedStructures, processRecursiveDisclosures)
import SDJWT.Internal.Issuance.Types
( TopLevelClaimsConfig(..)
, TopLevelClaimsResult(..)
, BuildSDJWTPayloadConfig(..)
, BuildSDJWTPayloadResult(..)
, CreateSDJWTConfig(..)
, CreateSDJWTWithDecoysConfig(..)
)
import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as Key
import qualified Data.Aeson.KeyMap as KeyMap
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Vector as V
import Data.List (sortBy, partition, find)
import Data.Ord (comparing)
import Text.Read (readMaybe)
import Data.Either (partitionEithers)
import Data.Maybe (mapMaybe)
import Control.Monad (replicateM)
import Control.Monad.Except (throwError)
markSelectivelyDisclosable
:: HashAlgorithm
-> T.Text
-> Aeson.Value
-> IO (Either SDJWTError (Digest, EncodedDisclosure))
markSelectivelyDisclosable :: HashAlgorithm
-> Text
-> Value
-> IO (Either SDJWTError (Digest, EncodedDisclosure))
markSelectivelyDisclosable HashAlgorithm
hashAlg Text
claimName Value
claimValue =
(ByteString -> Either SDJWTError (Digest, EncodedDisclosure))
-> IO ByteString
-> IO (Either SDJWTError (Digest, EncodedDisclosure))
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\ByteString
saltBytes ->
let salt :: Salt
salt = ByteString -> Salt
Salt ByteString
saltBytes
in case Salt -> Text -> Value -> Either SDJWTError EncodedDisclosure
createObjectDisclosure Salt
salt Text
claimName Value
claimValue of
Left SDJWTError
err -> SDJWTError -> Either SDJWTError (Digest, EncodedDisclosure)
forall a b. a -> Either a b
Left SDJWTError
err
Right EncodedDisclosure
encodedDisclosure ->
let digest :: Digest
digest = HashAlgorithm -> EncodedDisclosure -> Digest
computeDigest HashAlgorithm
hashAlg EncodedDisclosure
encodedDisclosure
in (Digest, EncodedDisclosure)
-> Either SDJWTError (Digest, EncodedDisclosure)
forall a b. b -> Either a b
Right (Digest
digest, EncodedDisclosure
encodedDisclosure)
) IO ByteString
forall (m :: * -> *). MonadIO m => m ByteString
generateSalt
buildSDJWTPayloadExceptT
:: BuildSDJWTPayloadConfig
-> SDJWTIO BuildSDJWTPayloadResult
buildSDJWTPayloadExceptT :: BuildSDJWTPayloadConfig -> SDJWTIO BuildSDJWTPayloadResult
buildSDJWTPayloadExceptT BuildSDJWTPayloadConfig
config = do
let hashAlg :: HashAlgorithm
hashAlg = BuildSDJWTPayloadConfig -> HashAlgorithm
buildHashAlg BuildSDJWTPayloadConfig
config
let selectiveClaimNames :: [Text]
selectiveClaimNames = BuildSDJWTPayloadConfig -> [Text]
buildSelectiveClaimNames BuildSDJWTPayloadConfig
config
let claims :: Object
claims = BuildSDJWTPayloadConfig -> Object
buildClaims BuildSDJWTPayloadConfig
config
let ([Text]
topLevelClaims, [[Text]]
nestedPaths) = [Text] -> ([Text], [[Text]])
partitionNestedPaths [Text]
selectiveClaimNames
let recursiveParents :: Set Text
recursiveParents = [Text] -> [[Text]] -> Set Text
identifyRecursiveParents [Text]
topLevelClaims [[Text]]
nestedPaths
let ([[Text]]
recursivePaths, [[Text]]
structuredPaths) = Set Text -> [[Text]] -> ([[Text]], [[Text]])
separateRecursiveAndStructuredPaths Set Text
recursiveParents [[Text]]
nestedPaths
(Object
structuredPayload, [EncodedDisclosure]
structuredDisclosures, Object
remainingClaimsAfterStructured) <-
IO (Either SDJWTError (Object, [EncodedDisclosure], Object))
-> ExceptT
SDJWTError
IO
(Either SDJWTError (Object, [EncodedDisclosure], Object))
forall a. IO a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (HashAlgorithm
-> [[Text]]
-> Object
-> IO (Either SDJWTError (Object, [EncodedDisclosure], Object))
processNestedStructures HashAlgorithm
hashAlg [[Text]]
structuredPaths Object
claims) ExceptT
SDJWTError
IO
(Either SDJWTError (Object, [EncodedDisclosure], Object))
-> (Either SDJWTError (Object, [EncodedDisclosure], Object)
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object))
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object)
forall a b.
ExceptT SDJWTError IO a
-> (a -> ExceptT SDJWTError IO b) -> ExceptT SDJWTError IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (SDJWTError
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object))
-> ((Object, [EncodedDisclosure], Object)
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object))
-> Either SDJWTError (Object, [EncodedDisclosure], Object)
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either SDJWTError
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object)
forall a. SDJWTError -> ExceptT SDJWTError IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (Object, [EncodedDisclosure], Object)
-> ExceptT SDJWTError IO (Object, [EncodedDisclosure], Object)
forall a. a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. Monad m => a -> m a
return
([(Text, Digest, EncodedDisclosure)]
recursiveParentInfo, [EncodedDisclosure]
recursiveDisclosures, Object
remainingClaimsAfterRecursive) <-
IO
(Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
-> ExceptT
SDJWTError
IO
(Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
forall a. IO a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (HashAlgorithm
-> [[Text]]
-> Object
-> IO
(Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
processRecursiveDisclosures HashAlgorithm
hashAlg [[Text]]
recursivePaths Object
remainingClaimsAfterStructured) ExceptT
SDJWTError
IO
(Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
-> (Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
forall a b.
ExceptT SDJWTError IO a
-> (a -> ExceptT SDJWTError IO b) -> ExceptT SDJWTError IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (SDJWTError
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
-> (([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure],
Object)
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object))
-> Either
SDJWTError
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either SDJWTError
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
forall a. SDJWTError -> ExceptT SDJWTError IO a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError ([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
-> ExceptT
SDJWTError
IO
([(Text, Digest, EncodedDisclosure)], [EncodedDisclosure], Object)
forall a. a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. Monad m => a -> m a
return
TopLevelClaimsResult
topLevelResult <- TopLevelClaimsConfig -> SDJWTIO TopLevelClaimsResult
processTopLevelSelectiveClaimsExceptT TopLevelClaimsConfig
{ topLevelHashAlg :: HashAlgorithm
topLevelHashAlg = HashAlgorithm
hashAlg
, topLevelRecursiveParents :: Set Text
topLevelRecursiveParents = Set Text
recursiveParents
, topLevelClaimNames :: [Text]
topLevelClaimNames = [Text]
topLevelClaims
, topLevelRemainingClaims :: Object
topLevelRemainingClaims = Object
remainingClaimsAfterRecursive
}
let recursiveParentDigests :: [Digest]
recursiveParentDigests = ((Text, Digest, EncodedDisclosure) -> Digest)
-> [(Text, Digest, EncodedDisclosure)] -> [Digest]
forall a b. (a -> b) -> [a] -> [b]
map (\(Text
_, Digest
digest, EncodedDisclosure
_) -> Digest
digest) [(Text, Digest, EncodedDisclosure)]
recursiveParentInfo
let ([EncodedDisclosure]
allDisclosures, [Digest]
allDigests) = [EncodedDisclosure]
-> [EncodedDisclosure]
-> [EncodedDisclosure]
-> [Digest]
-> [Digest]
-> ([EncodedDisclosure], [Digest])
combineAllDisclosuresAndDigests
[EncodedDisclosure]
structuredDisclosures [EncodedDisclosure]
recursiveDisclosures (TopLevelClaimsResult -> [EncodedDisclosure]
resultDisclosures TopLevelClaimsResult
topLevelResult)
[Digest]
recursiveParentDigests (TopLevelClaimsResult -> [Digest]
resultDigests TopLevelClaimsResult
topLevelResult)
let payloadObj :: Object
payloadObj = Object -> Object -> Object
forall v. KeyMap v -> KeyMap v -> KeyMap v
KeyMap.union Object
structuredPayload (TopLevelClaimsResult -> Object
resultRegularClaims TopLevelClaimsResult
topLevelResult)
let finalPayload :: Object
finalPayload = HashAlgorithm -> Object -> [Digest] -> Object
buildFinalPayloadObject HashAlgorithm
hashAlg Object
payloadObj [Digest]
allDigests
BuildSDJWTPayloadResult -> SDJWTIO BuildSDJWTPayloadResult
forall a. a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. Monad m => a -> m a
return BuildSDJWTPayloadResult
{ buildPayload :: Value
buildPayload = Object -> Value
Aeson.Object Object
finalPayload
, buildDisclosures :: [EncodedDisclosure]
buildDisclosures = [EncodedDisclosure]
allDisclosures
}
buildSDJWTPayload
:: HashAlgorithm
-> [T.Text]
-> Aeson.Object
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
buildSDJWTPayload :: HashAlgorithm
-> [Text]
-> Object
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
buildSDJWTPayload HashAlgorithm
hashAlg [Text]
selectiveClaimNames Object
claims = do
let config :: BuildSDJWTPayloadConfig
config = BuildSDJWTPayloadConfig
{ buildHashAlg :: HashAlgorithm
buildHashAlg = HashAlgorithm
hashAlg
, buildSelectiveClaimNames :: [Text]
buildSelectiveClaimNames = [Text]
selectiveClaimNames
, buildClaims :: Object
buildClaims = Object
claims
}
Either SDJWTError BuildSDJWTPayloadResult
result <- SDJWTIO BuildSDJWTPayloadResult
-> IO (Either SDJWTError BuildSDJWTPayloadResult)
forall a. SDJWTIO a -> IO (Either SDJWTError a)
runSDJWTIO (BuildSDJWTPayloadConfig -> SDJWTIO BuildSDJWTPayloadResult
buildSDJWTPayloadExceptT BuildSDJWTPayloadConfig
config)
case Either SDJWTError BuildSDJWTPayloadResult
result of
Left SDJWTError
err -> Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTError -> Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
forall a b. a -> Either a b
Left SDJWTError
err)
Right BuildSDJWTPayloadResult
res -> do
let payload :: SDJWTPayload
payload = SDJWTPayload
{ sdAlg :: Maybe HashAlgorithm
sdAlg = HashAlgorithm -> Maybe HashAlgorithm
forall a. a -> Maybe a
Just HashAlgorithm
hashAlg
, payloadValue :: Value
payloadValue = BuildSDJWTPayloadResult -> Value
buildPayload BuildSDJWTPayloadResult
res
}
Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ((SDJWTPayload, [EncodedDisclosure])
-> Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
forall a b. b -> Either a b
Right (SDJWTPayload
payload, BuildSDJWTPayloadResult -> [EncodedDisclosure]
buildDisclosures BuildSDJWTPayloadResult
res))
createSDJWT
:: JWKLike jwk => Maybe T.Text
-> Maybe T.Text
-> HashAlgorithm
-> jwk
-> [T.Text]
-> Aeson.Object
-> IO (Either SDJWTError SDJWT)
createSDJWT :: forall jwk.
JWKLike jwk =>
Maybe Text
-> Maybe Text
-> HashAlgorithm
-> jwk
-> [Text]
-> Object
-> IO (Either SDJWTError SDJWT)
createSDJWT Maybe Text
mbTyp Maybe Text
mbKid HashAlgorithm
hashAlg jwk
issuerPrivateKeyJWK [Text]
selectiveClaimNames Object
claims = do
Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
result <- HashAlgorithm
-> [Text]
-> Object
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
buildSDJWTPayload HashAlgorithm
hashAlg [Text]
selectiveClaimNames Object
claims
case Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
result of
Left SDJWTError
err -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left SDJWTError
err)
Right (SDJWTPayload
payload, [EncodedDisclosure]
sdDisclosures) -> do
Either SDJWTError Text
signedJWTResult <- Maybe Text
-> Maybe Text -> jwk -> Value -> IO (Either SDJWTError Text)
forall jwk.
JWKLike jwk =>
Maybe Text
-> Maybe Text -> jwk -> Value -> IO (Either SDJWTError Text)
signJWTWithHeaders Maybe Text
mbTyp Maybe Text
mbKid jwk
issuerPrivateKeyJWK (SDJWTPayload -> Value
payloadValue SDJWTPayload
payload)
case Either SDJWTError Text
signedJWTResult of
Left SDJWTError
err -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left SDJWTError
err)
Right Text
signedJWT -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT))
-> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a b. (a -> b) -> a -> b
$ SDJWT -> Either SDJWTError SDJWT
forall a b. b -> Either a b
Right (SDJWT -> Either SDJWTError SDJWT)
-> SDJWT -> Either SDJWTError SDJWT
forall a b. (a -> b) -> a -> b
$ SDJWT
{ issuerSignedJWT :: Text
issuerSignedJWT = Text
signedJWT
, disclosures :: [EncodedDisclosure]
disclosures = [EncodedDisclosure]
sdDisclosures
}
createSDJWTWithDecoys
:: JWKLike jwk => Maybe T.Text
-> Maybe T.Text
-> HashAlgorithm
-> jwk
-> [T.Text]
-> Aeson.Object
-> Int
-> IO (Either SDJWTError SDJWT)
createSDJWTWithDecoys :: forall jwk.
JWKLike jwk =>
Maybe Text
-> Maybe Text
-> HashAlgorithm
-> jwk
-> [Text]
-> Object
-> Int
-> IO (Either SDJWTError SDJWT)
createSDJWTWithDecoys Maybe Text
mbTyp Maybe Text
mbKid HashAlgorithm
hashAlg jwk
issuerPrivateKeyJWK [Text]
selectiveClaimNames Object
claims Int
decoyCount
| Int
decoyCount Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 = Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT))
-> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a b. (a -> b) -> a -> b
$ SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError SDJWT)
-> SDJWTError -> Either SDJWTError SDJWT
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDisclosureFormat Text
"decoyCount must be >= 0"
| Int
decoyCount Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 = Maybe Text
-> Maybe Text
-> HashAlgorithm
-> jwk
-> [Text]
-> Object
-> IO (Either SDJWTError SDJWT)
forall jwk.
JWKLike jwk =>
Maybe Text
-> Maybe Text
-> HashAlgorithm
-> jwk
-> [Text]
-> Object
-> IO (Either SDJWTError SDJWT)
createSDJWT Maybe Text
mbTyp Maybe Text
mbKid HashAlgorithm
hashAlg jwk
issuerPrivateKeyJWK [Text]
selectiveClaimNames Object
claims
| Bool
otherwise = do
Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
result <- HashAlgorithm
-> [Text]
-> Object
-> IO (Either SDJWTError (SDJWTPayload, [EncodedDisclosure]))
buildSDJWTPayload HashAlgorithm
hashAlg [Text]
selectiveClaimNames Object
claims
case Either SDJWTError (SDJWTPayload, [EncodedDisclosure])
result of
Left SDJWTError
err -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left SDJWTError
err)
Right (SDJWTPayload
payload, [EncodedDisclosure]
sdDisclosures) -> do
[Digest]
decoys <- Int -> IO Digest -> IO [Digest]
forall (m :: * -> *) a. Applicative m => Int -> m a -> m [a]
replicateM Int
decoyCount (HashAlgorithm -> IO Digest
addDecoyDigest HashAlgorithm
hashAlg)
case SDJWTPayload -> Value
payloadValue SDJWTPayload
payload of
Aeson.Object Object
obj -> do
case Key -> Object -> Maybe Value
forall v. Key -> KeyMap v -> Maybe v
KeyMap.lookup (Text -> Key
Key.fromText Text
"_sd") Object
obj of
Just (Aeson.Array Array
sdArray) -> do
let decoyDigests :: [Value]
decoyDigests = (Digest -> Value) -> [Digest] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Value
Aeson.String (Text -> Value) -> (Digest -> Text) -> Digest -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Digest -> Text
unDigest) [Digest]
decoys
let updatedSDArray :: Array
updatedSDArray = Array
sdArray Array -> Array -> Array
forall a. Semigroup a => a -> a -> a
<> [Value] -> Array
forall a. [a] -> Vector a
V.fromList [Value]
decoyDigests
let updatedObj :: Object
updatedObj = Key -> Value -> Object -> Object
forall v. Key -> v -> KeyMap v -> KeyMap v
KeyMap.insert (Text -> Key
Key.fromText Text
"_sd") (Array -> Value
Aeson.Array Array
updatedSDArray) Object
obj
let updatedPayload :: SDJWTPayload
updatedPayload = SDJWTPayload
payload { payloadValue = Aeson.Object updatedObj }
Either SDJWTError Text
signedJWTResult <- Maybe Text
-> Maybe Text -> jwk -> Value -> IO (Either SDJWTError Text)
forall jwk.
JWKLike jwk =>
Maybe Text
-> Maybe Text -> jwk -> Value -> IO (Either SDJWTError Text)
signJWTWithHeaders Maybe Text
mbTyp Maybe Text
mbKid jwk
issuerPrivateKeyJWK (SDJWTPayload -> Value
payloadValue SDJWTPayload
updatedPayload)
case Either SDJWTError Text
signedJWTResult of
Left SDJWTError
err -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left SDJWTError
err)
Right Text
signedJWT -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT))
-> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a b. (a -> b) -> a -> b
$ SDJWT -> Either SDJWTError SDJWT
forall a b. b -> Either a b
Right (SDJWT -> Either SDJWTError SDJWT)
-> SDJWT -> Either SDJWTError SDJWT
forall a b. (a -> b) -> a -> b
$ SDJWT
{ issuerSignedJWT :: Text
issuerSignedJWT = Text
signedJWT
, disclosures :: [EncodedDisclosure]
disclosures = [EncodedDisclosure]
sdDisclosures
}
Maybe Value
_ -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT))
-> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a b. (a -> b) -> a -> b
$ SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError SDJWT)
-> SDJWTError -> Either SDJWTError SDJWT
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDisclosureFormat Text
"Payload does not contain _sd array"
Value
_ -> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT))
-> Either SDJWTError SDJWT -> IO (Either SDJWTError SDJWT)
forall a b. (a -> b) -> a -> b
$ SDJWTError -> Either SDJWTError SDJWT
forall a b. a -> Either a b
Left (SDJWTError -> Either SDJWTError SDJWT)
-> SDJWTError -> Either SDJWTError SDJWT
forall a b. (a -> b) -> a -> b
$ Text -> SDJWTError
InvalidDisclosureFormat Text
"Payload is not an object"
addHolderKeyToClaims
:: T.Text
-> Aeson.Object
-> Aeson.Object
addHolderKeyToClaims :: Text -> Object -> Object
addHolderKeyToClaims Text
holderPublicKeyJWK Object
claims =
let
jwkValue :: Value
jwkValue = case ByteString -> Either String Value
forall a. FromJSON a => ByteString -> Either String a
Aeson.eitherDecodeStrict (Text -> ByteString
TE.encodeUtf8 Text
holderPublicKeyJWK) :: Either String Aeson.Value of
Left String
_ -> Text -> Value
Aeson.String Text
holderPublicKeyJWK
Right Value
parsedJWK -> Value
parsedJWK
cnfValue :: Value
cnfValue = Object -> Value
Aeson.Object (Object -> Value) -> Object -> Value
forall a b. (a -> b) -> a -> b
$ [(Key, Value)] -> Object
forall v. [(Key, v)] -> KeyMap v
KeyMap.fromList [(Key
"jwk", Value
jwkValue)]
in
Key -> Value -> Object -> Object
forall v. Key -> v -> KeyMap v -> KeyMap v
KeyMap.insert Key
"cnf" Value
cnfValue Object
claims
addDecoyDigest
:: HashAlgorithm
-> IO Digest
addDecoyDigest :: HashAlgorithm -> IO Digest
addDecoyDigest HashAlgorithm
hashAlg =
(ByteString -> Digest) -> IO ByteString -> IO Digest
forall a b. (a -> b) -> IO a -> IO b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\ByteString
randomBytes ->
let hashBytes :: ByteString
hashBytes = HashAlgorithm -> ByteString -> ByteString
hashToBytes HashAlgorithm
hashAlg ByteString
randomBytes
digestText :: Text
digestText = ByteString -> Text
base64urlEncode ByteString
hashBytes
in Text -> Digest
Digest Text
digestText
) IO ByteString
forall (m :: * -> *). MonadIO m => m ByteString
generateSalt
sortDigests :: [Digest] -> [Digest]
sortDigests :: [Digest] -> [Digest]
sortDigests = (Digest -> Digest -> Ordering) -> [Digest] -> [Digest]
forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy ((Digest -> Text) -> Digest -> Digest -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing Digest -> Text
unDigest)
identifyRecursiveParents :: [T.Text] -> [[T.Text]] -> Set.Set T.Text
identifyRecursiveParents :: [Text] -> [[Text]] -> Set Text
identifyRecursiveParents [Text]
topLevelClaims [[Text]]
nestedPaths =
let getFirstSegment :: [a] -> a
getFirstSegment [] = a
""
getFirstSegment (a
seg:[a]
_) = a
seg
in [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList (([Text] -> Text) -> [[Text]] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map [Text] -> Text
forall {a}. IsString a => [a] -> a
getFirstSegment [[Text]]
nestedPaths) Set Text -> Set Text -> Set Text
forall a. Ord a => Set a -> Set a -> Set a
`Set.intersection` [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList [Text]
topLevelClaims
separateRecursiveAndStructuredPaths
:: Set.Set T.Text
-> [[T.Text]]
-> ([[T.Text]], [[T.Text]])
separateRecursiveAndStructuredPaths :: Set Text -> [[Text]] -> ([[Text]], [[Text]])
separateRecursiveAndStructuredPaths Set Text
recursiveParents [[Text]]
nestedPaths =
([Text] -> Bool) -> [[Text]] -> ([[Text]], [[Text]])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (\[Text]
path -> case [Text]
path of
[] -> Bool
False
(Text
first:[Text]
_) -> Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
Set.member Text
first Set Text
recursiveParents) [[Text]]
nestedPaths
processTopLevelSelectiveClaimsExceptT
:: TopLevelClaimsConfig
-> SDJWTIO TopLevelClaimsResult
processTopLevelSelectiveClaimsExceptT :: TopLevelClaimsConfig -> SDJWTIO TopLevelClaimsResult
processTopLevelSelectiveClaimsExceptT TopLevelClaimsConfig
config = do
let topLevelClaimsWithoutRecursive :: [Text]
topLevelClaimsWithoutRecursive = (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.notMember` TopLevelClaimsConfig -> Set Text
topLevelRecursiveParents TopLevelClaimsConfig
config) (TopLevelClaimsConfig -> [Text]
topLevelClaimNames TopLevelClaimsConfig
config)
let selectiveClaims :: Object
selectiveClaims = (Key -> Value -> Bool) -> Object -> Object
forall v. (Key -> v -> Bool) -> KeyMap v -> KeyMap v
KeyMap.filterWithKey
(\Key
k Value
_ -> Key -> Text
Key.toText Key
k Text -> [Text] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
topLevelClaimsWithoutRecursive) (TopLevelClaimsConfig -> Object
topLevelRemainingClaims TopLevelClaimsConfig
config)
let regularClaims :: Object
regularClaims = (Key -> Value -> Bool) -> Object -> Object
forall v. (Key -> v -> Bool) -> KeyMap v -> KeyMap v
KeyMap.filterWithKey
(\Key
k Value
_ -> Key -> Text
Key.toText Key
k Text -> [Text] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Text]
topLevelClaimsWithoutRecursive) (TopLevelClaimsConfig -> Object
topLevelRemainingClaims TopLevelClaimsConfig
config)
[Either SDJWTError (Digest, EncodedDisclosure)]
disclosureResults <- IO [Either SDJWTError (Digest, EncodedDisclosure)]
-> ExceptT
SDJWTError IO [Either SDJWTError (Digest, EncodedDisclosure)]
forall a. IO a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO [Either SDJWTError (Digest, EncodedDisclosure)]
-> ExceptT
SDJWTError IO [Either SDJWTError (Digest, EncodedDisclosure)])
-> IO [Either SDJWTError (Digest, EncodedDisclosure)]
-> ExceptT
SDJWTError IO [Either SDJWTError (Digest, EncodedDisclosure)]
forall a b. (a -> b) -> a -> b
$ ((Key, Value)
-> IO (Either SDJWTError (Digest, EncodedDisclosure)))
-> [(Key, Value)]
-> IO [Either SDJWTError (Digest, EncodedDisclosure)]
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 (\(Key
k, Value
v) -> HashAlgorithm
-> Text
-> Value
-> IO (Either SDJWTError (Digest, EncodedDisclosure))
markSelectivelyDisclosable (TopLevelClaimsConfig -> HashAlgorithm
topLevelHashAlg TopLevelClaimsConfig
config) (Key -> Text
Key.toText Key
k) Value
v) (Object -> [(Key, Value)]
forall v. KeyMap v -> [(Key, v)]
KeyMap.toList Object
selectiveClaims)
[Either SDJWTError (Digest, EncodedDisclosure)]
-> ([(Digest, EncodedDisclosure)] -> SDJWTIO TopLevelClaimsResult)
-> SDJWTIO TopLevelClaimsResult
forall (m :: * -> *) a b.
Monad m =>
[Either SDJWTError a]
-> ([a] -> ExceptT SDJWTError m b) -> ExceptT SDJWTError m b
partitionAndHandle [Either SDJWTError (Digest, EncodedDisclosure)]
disclosureResults (([(Digest, EncodedDisclosure)] -> SDJWTIO TopLevelClaimsResult)
-> SDJWTIO TopLevelClaimsResult)
-> ([(Digest, EncodedDisclosure)] -> SDJWTIO TopLevelClaimsResult)
-> SDJWTIO TopLevelClaimsResult
forall a b. (a -> b) -> a -> b
$ \[(Digest, EncodedDisclosure)]
successes -> do
let ([Digest]
topLevelDigests, [EncodedDisclosure]
topLevelDisclosures) = [(Digest, EncodedDisclosure)] -> ([Digest], [EncodedDisclosure])
forall a b. [(a, b)] -> ([a], [b])
unzip [(Digest, EncodedDisclosure)]
successes
TopLevelClaimsResult -> SDJWTIO TopLevelClaimsResult
forall a. a -> ExceptT SDJWTError IO a
forall (m :: * -> *) a. Monad m => a -> m a
return TopLevelClaimsResult
{ resultDigests :: [Digest]
resultDigests = [Digest]
topLevelDigests
, resultDisclosures :: [EncodedDisclosure]
resultDisclosures = [EncodedDisclosure]
topLevelDisclosures
, resultRegularClaims :: Object
resultRegularClaims = Object
regularClaims
}
combineAllDisclosuresAndDigests
:: [EncodedDisclosure]
-> [EncodedDisclosure]
-> [EncodedDisclosure]
-> [Digest]
-> [Digest]
-> ([EncodedDisclosure], [Digest])
combineAllDisclosuresAndDigests :: [EncodedDisclosure]
-> [EncodedDisclosure]
-> [EncodedDisclosure]
-> [Digest]
-> [Digest]
-> ([EncodedDisclosure], [Digest])
combineAllDisclosuresAndDigests [EncodedDisclosure]
structuredDisclosures [EncodedDisclosure]
recursiveDisclosures [EncodedDisclosure]
topLevelDisclosures [Digest]
recursiveParentDigests [Digest]
topLevelDigests =
let allDisclosures :: [EncodedDisclosure]
allDisclosures = [EncodedDisclosure]
structuredDisclosures [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
recursiveDisclosures [EncodedDisclosure] -> [EncodedDisclosure] -> [EncodedDisclosure]
forall a. [a] -> [a] -> [a]
++ [EncodedDisclosure]
topLevelDisclosures
allDigests :: [Digest]
allDigests = [Digest]
recursiveParentDigests [Digest] -> [Digest] -> [Digest]
forall a. [a] -> [a] -> [a]
++ [Digest]
topLevelDigests
in ([EncodedDisclosure]
allDisclosures, [Digest]
allDigests)
buildFinalPayloadObject
:: HashAlgorithm
-> Aeson.Object
-> [Digest]
-> Aeson.Object
buildFinalPayloadObject :: HashAlgorithm -> Object -> [Digest] -> Object
buildFinalPayloadObject HashAlgorithm
hashAlg Object
basePayload [Digest]
allDigests =
let payloadWithAlg :: Object
payloadWithAlg = Key -> Value -> Object -> Object
forall v. Key -> v -> KeyMap v -> KeyMap v
KeyMap.insert Key
"_sd_alg" (Text -> Value
Aeson.String (HashAlgorithm -> Text
hashAlgorithmToText HashAlgorithm
hashAlg)) Object
basePayload
in if [Digest] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Digest]
allDigests
then Object
payloadWithAlg
else let sortedDigests :: [Value]
sortedDigests = (Digest -> Value) -> [Digest] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map (Text -> Value
Aeson.String (Text -> Value) -> (Digest -> Text) -> Digest -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Digest -> Text
unDigest) ([Digest] -> [Digest]
sortDigests [Digest]
allDigests)
in Key -> Value -> Object -> Object
forall v. Key -> v -> KeyMap v -> KeyMap v
KeyMap.insert Key
"_sd" (Array -> Value
Aeson.Array ([Value] -> Array
forall a. [a] -> Vector a
V.fromList [Value]
sortedDigests)) Object
payloadWithAlg
partitionNestedPaths :: [T.Text] -> ([T.Text], [[T.Text]])
partitionNestedPaths :: [Text] -> ([Text], [[Text]])
partitionNestedPaths [Text]
claimNames =
let ([Text]
topLevel, [Text]
nested) = (Text -> Bool) -> [Text] -> ([Text], [Text])
forall a. (a -> Bool) -> [a] -> ([a], [a])
partition (Bool -> Bool
not (Bool -> Bool) -> (Text -> Bool) -> Text -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text -> Bool
T.isInfixOf Text
"/") [Text]
claimNames
nestedPaths :: [[Text]]
nestedPaths = (Text -> Maybe [Text]) -> [Text] -> [[Text]]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Text -> Maybe [Text]
parseJSONPointerPath [Text]
nested
unescapedTopLevel :: [Text]
unescapedTopLevel = (Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
unescapeJSONPointer [Text]
topLevel
in ([Text]
unescapedTopLevel, [[Text]]
nestedPaths)
where
parseJSONPointerPath :: T.Text -> Maybe [T.Text]
parseJSONPointerPath :: Text -> Maybe [Text]
parseJSONPointerPath Text
path = do
let segments :: [Text]
segments = Text -> [Text]
splitJSONPointer Text
path
case [Text]
segments of
[] -> Maybe [Text]
forall a. Maybe a
Nothing
[Text
_] -> Maybe [Text]
forall a. Maybe a
Nothing
[Text]
_ -> [Text] -> Maybe [Text]
forall a. a -> Maybe a
Just ((Text -> Text) -> [Text] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
unescapeJSONPointer [Text]
segments)