module Handler.StorageKey ( postStorageKeyR ) where import Import import Crypto.Hash import qualified Data.Aeson as Aeson import qualified Data.ByteString.Base64 as Base64 (encode, decodeLenient) import qualified Data.Binary as Binary (encode) import qualified Crypto.KDF.HKDF as HKDF {-# ANN module ("HLint: ignore Use newtype instead of data" :: String) #-} data StorageKeyType = SKTExamCorrect { _sktExam :: CryptoUUIDExam } deriveJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 1 , constructorTagModifier = camelToPathPiece' 1 , tagSingleConstructors = True } ''StorageKeyType data StorageKeyRequest = StorageKeyRequest { skReqType :: StorageKeyType , skReqTimestamp :: Maybe UTCTime , skReqSalt :: Maybe Text , skReqLength :: Int } deriveJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 , omitNothingFields = True } ''StorageKeyRequest data StorageKeyResponse = StorageKeyResponse { skResKey :: Text , skResTimestamp :: UTCTime , skResSalt :: Text } deriveJSON defaultOptions { fieldLabelModifier = camelToPathPiece' 2 , constructorTagModifier = camelToPathPiece' 2 } ''StorageKeyResponse postStorageKeyR :: Handler Value postStorageKeyR = do uid <- requireAuthId sbKey <- secretBoxKey StorageKeyRequest{..} <- requireCheckJsonBody now <- liftIO getCurrentTime let (lBound,rBound) = case skReqType of SKTExamCorrect _ -> (-30*nominalDay, nominalDay) timestampInBounds = maybe False (\ts -> addUTCTime lBound now < ts && addUTCTime rBound now > ts) skReqTimestamp timestamp = if | Just ts <- skReqTimestamp, timestampInBounds -> ts | otherwise -> now salt <- let sltSize = hashDigestSize SHA3_256 in if | Just slt <- Base64.decodeLenient . encodeUtf8 <$> skReqSalt , timestampInBounds , length slt == sltSize -> return slt | otherwise -> pack . take sltSize <$> liftIO getRandoms let ikm = (toStrict . Aeson.encode) (skReqType, uid, sbKey) key = HKDF.expand (HKDF.extract salt ikm :: HKDF.PRK SHA3_256) (toStrict $ Binary.encode (timestamp,skReqLength)) skReqLength return $ toJSON StorageKeyResponse { skResKey = (decodeUtf8 . Base64.encode) key , skResTimestamp = timestamp , skResSalt = (decodeUtf8 . Base64.encode) salt }