{-# language RecordWildCards #-}

module Fedi.Crypto where

import Crypto.Hash qualified as Crypto
import Data.ByteArray qualified as BA
import Crypto.PubKey.RSA.PKCS15 qualified as Crypto
import Crypto.Store.X509 qualified as Crypto
import Crypto.Store.PKCS8 qualified as Crypto
import Data.X509 qualified as Crypto
import Fedi.Helpers
import Data.ByteString.Base64 qualified as Base64
import Data.Base64.Types qualified as Base64
import Data.Text qualified as T

verifyPub :: MonadThrow m => ByteString -> ByteString -> ByteString -> m Bool
verifyPub pubkeypem sig message = do
  pubkey <-
    case Crypto.readPubKeyFileFromMemory pubkeypem of
      [Crypto.PubKeyRSA pubkey'] -> pure pubkey'
      _ -> throw "failed to read pubkey pem"
  pure $ Crypto.verify (Just Crypto.SHA256) pubkey message sig

sign :: FilePath -> ByteString -> IO Signed
sign privatePemFile message = do
  -- get private key
  privkeypem <- Crypto.readKeyFile privatePemFile
  privateKey <- case privkeypem of
    [Crypto.Unprotected (Crypto.PrivKeyRSA privkey)] -> pure privkey
    _ -> throw $ "error reading local private key from '" <> privatePemFile <> "'."

  -- sign message
  signedMessage <-
    Crypto.sign Nothing (Just Crypto.SHA256) privateKey message
      & either (throw . show) pure

  -- return
  pure Signed{..}

newtype Signed
  = Signed
    { signedMessage :: ByteString
    }
  deriving Show

ppSigned :: Signed -> String
ppSigned signed =
  unlines
    [ "Signature"
    , "{ signedMessage = " <> encodeBase64String signed.signedMessage
    , "}"
    ]

encodeBase64 :: ByteString -> ByteString
encodeBase64 = Base64.extractBase64 . Base64.encodeBase64'

encodeBase64String :: ByteString -> String
encodeBase64String = T.unpack . Base64.extractBase64 . Base64.encodeBase64

decodeBase64 :: ByteString -> ByteString
decodeBase64 = Base64.decodeBase64Lenient

makeDigest :: ByteString -> ByteString
makeDigest message =
   BA.convert (Crypto.hash message :: Crypto.Digest Crypto.SHA256)