packages feed

paddle-0.1.0.0: src/Paddle/WebHook/Signature.hs

module Paddle.WebHook.Signature where 

import Prelude ()
import qualified Prelude
import Protolude hiding (toS)
import Protolude.Conv
import qualified Data.Map.Strict as M
import qualified Data.ByteString.Builder as BSB
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy as LBS
import qualified OpenSSL as SSL
import qualified OpenSSL.EVP.Digest as SSL
import qualified OpenSSL.EVP.Verify as SSL
import qualified Data.ByteString.Base64 as Base64

import Paddle.Env (Env(..))

type SignatureBody = M.Map Text [Text]

-- Given all fields in webhook request, validate against their signature
validateSignature :: (MonadIO m) => Env -> SignatureBody -> m (Either Text ())
validateSignature env rawFields =
  let 
    fields =  M.fromList $ map (\(k ,v) -> (toS k, toS (maybe "" identity (head v)))) (M.toList rawFields)

    signature :: Either Prelude.String ByteString
    signature = maybeToEither "Missing signature field." (M.lookup "p_signature" fields)

    signatureDecode :: ByteString -> Either Prelude.String ByteString
    signatureDecode = Base64.decode . toS

    -- we need to verify the signature against the PHP-serialized request parameters (excluding p_signature)
    -- here's a gist that does it in Swift: https://gist.github.com/drewmccormack/a51b18ffeda8f596a11a8623481344d8
    serializeString str = "s:" <> BSB.intDec (BS.length str) <> ":\"" <> BSB.byteString str <> "\";"
    serialize sig = (\x -> Right (x, sig)) <$> LBS.toStrict $ BSB.toLazyByteString $
            "a:" <> BSB.intDec (M.size fields - 1) <> ":{" <>
              foldMap (\( key, value ) -> serializeString key <> serializeString value) (M.toAscList $ M.delete "p_signature" fields)
            <> "}"
  in do
    case signature >>= signatureDecode >>= serialize of
      Left err -> return $ Left (toS err)
      Right (serializedFields, sigBytes) -> liftIO $ SSL.withOpenSSL $ do
        Just sha1 <- SSL.getDigestByName "SHA1"
        verifyRes <- SSL.verifyBS sha1 sigBytes (pctxPubKey env) serializedFields

        if (verifyRes == SSL.VerifySuccess)
        then return $ Right ()
        else return $ Left "Request has invalid signature"