packages feed

hOpenPGP-3.3: Codec/Encryption/OpenPGP/Message.hs

-- Message.hs: OpenPGP (RFC9580) message helpers
-- Copyright © 2026  Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module Codec.Encryption.OpenPGP.Message
    ( Passphrase
    , EncryptedPayload
    , mkEncryptedPayload
    , encryptedPayloadBytes
    , ClearPayload
    , mkClearPayload
    , clearPayloadBytes
    , WrappedSessionMaterial
    , SigningAlgorithm (..)
    , SecretKeyFor
    , VersionedPKPayload
    , asV4PKPayload
    , asV6PKPayload
    , Signer
    , SigningCapability
    , mkRSASignerV4
    , mkRSASignerV6
    , mkEd25519SignerV4
    , mkEd25519SignerV6
    , mkEd448SignerV4
    , mkEd448SignerV6
    , MessageParseFailure (..)
    , renderMessageParseFailure
    , MDCFailure (..)
    , AEADFailure (..)
    , renderAEADFailure
    , PayloadDecryptFailure (..)
    , renderPayloadDecryptFailure
    , MessageDecryptFailure (..)
    , renderMessageDecryptFailure
    , MessageEncryptFailure (..)
    , renderMessageEncryptFailure
    , MessageError (..)
    , renderMessageError
    , SessionMaterialExposure (..)
    , EncryptMessageProfile
    , EncryptMessageOptions (..)
    , RecoveredSessionMaterial (..)
    , encryptMessage
    , decryptMessage
    , signMessage
    , signMessageWith
    , ConduitMessage.VerificationPolicy (..)
    , ConduitMessage.VerificationOptions (..)
    , ConduitMessage.defaultVerificationOptions
    , verifySignedMessage
    ) where

import Control.Monad (foldM)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import qualified Crypto.PubKey.Ed25519 as Ed25519
import qualified Crypto.PubKey.Ed448 as Ed448
import qualified Crypto.PubKey.RSA.Types as RSATypes
import Crypto.Random.Types (MonadRandom, getRandomBytes)
import Data.Bifunctor (bimap, first)
import Data.Binary (put)
import Data.Binary.Put (runPut)
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import Data.Functor.Identity (Identity (..), runIdentity)
import Data.Kind (Type)
import Data.Word (Word8)

import Codec.Encryption.OpenPGP.BlockCipher
    ( CipherError
    , keySize
    , renderCipherError
    )
import Codec.Encryption.OpenPGP.CFB
    ( OpenPGPCFBModeW (..)
    , decryptOpenPGPCfb
    , decryptPreservingNonce
    , encryptOpenPGPCfbRaw
    )
import Codec.Encryption.OpenPGP.Encrypt
    ( buildOnePassSignature
    , encryptSEIPDv2WithSKESKBlock
    , renderOPSBuildError
    )
import Codec.Encryption.OpenPGP.Fingerprint
    ( eightOctetKeyID
    , fingerprint
    )
import Codec.Encryption.OpenPGP.Policy
    ( HashAlgorithmW (..)
    , OpenPGPPolicy
    , OpenPGPRFCW (..)
    , defaultPolicy
    , deprecatedHashAlgorithms
    , messageDefaultAEADAlgorithm
    , messageDefaultChunkSize
    , messageSEIPDv2SaltOctets
    , policyGenerationDeprecations
    , policyMessageEncryption
    , supportsSEIPDv2Symmetric
    )
import Codec.Encryption.OpenPGP.S2K
    ( S2KError (..)
    , renderS2KError
    , skesk2SessionKey
    , string2Key
    )
import Codec.Encryption.OpenPGP.SEIPDv1
    ( MDCFailure (..)
    , mdcTrailerForSEIPDv1
    , renderMDCFailure
    , validateSEIPD1MDC
    )
import Codec.Encryption.OpenPGP.SEIPDv2
    ( SEIPDv2Failure (..)
    , decryptSKESK6SessionKey
    , deriveSKESK6KEK
    , renderSEIPDv2Failure
    )
import Codec.Encryption.OpenPGP.Serialize (parsePkts)
import Codec.Encryption.OpenPGP.Signatures
    ( SignError (..)
    , VerificationError
    , renderSignError
    , signDataWithEd25519Builder
    , signDataWithEd25519V6Builder
    , signDataWithEd448Builder
    , signDataWithEd448V6Builder
    , signDataWithRSABuilder
    , signDataWithRSAV6Builder
    )
import Codec.Encryption.OpenPGP.Subpackets
    ( addHashedSubs
    , addUnhashedSubs
    , listToHashedSubs
    , listToUnhashedSubs
    , sigBuilderInitTyped
    , sigBuilderInitV6Typed
    )
import Codec.Encryption.OpenPGP.Types
import qualified Codec.Encryption.OpenPGP.Types.Internal.Base as PKA
import Data.Conduit.OpenPGP.Decrypt (decryptSEIPDv2Payload)
import qualified Data.Conduit.OpenPGP.Message as ConduitMessage

newtype EncryptedPayload = EncryptedPayload {unEncryptedPayload :: BL.ByteString}
    deriving (Eq, Ord, Show)

newtype ClearPayload = ClearPayload {unClearPayload :: BL.ByteString}
    deriving (Eq, Ord, Show)

newtype WrappedSessionMaterial = WrappedSessionMaterial
    {unWrappedSessionMaterial :: B.ByteString}
    deriving (Eq, Ord, Show)

data SigningAlgorithm = AlgoRSA | AlgoEd25519 | AlgoEd448

type family SecretKeyFor (alg :: SigningAlgorithm) where
    SecretKeyFor 'AlgoRSA = RSATypes.PrivateKey
    SecretKeyFor 'AlgoEd25519 = Ed25519.SecretKey
    SecretKeyFor 'AlgoEd448 = Ed448.SecretKey

type family KeyVersionForSig (v :: Type) :: KeyVersion where
    KeyVersionForSig V4Sig = 'V4
    KeyVersionForSig V6Sig = 'V6

data VersionedPKPayload (v :: KeyVersion) where
    VersionedPKPayloadV4 :: PKPayload 'V4 -> VersionedPKPayload 'V4
    VersionedPKPayloadV6 :: PKPayload 'V6 -> VersionedPKPayload 'V6

asV4PKPayload
    :: SomePKPayload -> Either String (VersionedPKPayload 'V4)
asV4PKPayload (SomePKPayload pk@(PKPayloadV4 _ _ _)) =
    Right (VersionedPKPayloadV4 pk)
asV4PKPayload _ = Left "Expected a v4 PKPayload"

asV6PKPayload
    :: SomePKPayload -> Either String (VersionedPKPayload 'V6)
asV6PKPayload (SomePKPayload pk@(PKPayloadV6 _ _ _)) =
    Right (VersionedPKPayloadV6 pk)
asV6PKPayload _ = Left "Expected a v6 PKPayload"

data Signer (alg :: SigningAlgorithm) (v :: Type) where
    RSASigner
        :: VersionedPKPayload (KeyVersionForSig v)
        -> SecretKeyFor 'AlgoRSA
        -> Signer 'AlgoRSA v
    Ed25519Signer
        :: VersionedPKPayload (KeyVersionForSig v)
        -> SecretKeyFor 'AlgoEd25519
        -> Signer 'AlgoEd25519 v
    Ed448Signer
        :: VersionedPKPayload (KeyVersionForSig v)
        -> SecretKeyFor 'AlgoEd448
        -> Signer 'AlgoEd448 v

class SigningCapability (alg :: SigningAlgorithm) v
instance SigningCapability 'AlgoRSA V4Sig
instance SigningCapability 'AlgoRSA V6Sig
instance SigningCapability 'AlgoEd25519 V4Sig
instance SigningCapability 'AlgoEd25519 V6Sig
instance SigningCapability 'AlgoEd448 V4Sig
instance SigningCapability 'AlgoEd448 V6Sig

mkRSASignerV4
    :: VersionedPKPayload 'V4
    -> SecretKeyFor 'AlgoRSA
    -> Signer 'AlgoRSA V4Sig
mkRSASignerV4 = RSASigner

mkRSASignerV6
    :: VersionedPKPayload 'V6
    -> SecretKeyFor 'AlgoRSA
    -> Signer 'AlgoRSA V6Sig
mkRSASignerV6 = RSASigner

mkEd25519SignerV4
    :: VersionedPKPayload 'V4
    -> SecretKeyFor 'AlgoEd25519
    -> Signer 'AlgoEd25519 V4Sig
mkEd25519SignerV4 = Ed25519Signer

mkEd25519SignerV6
    :: VersionedPKPayload 'V6
    -> SecretKeyFor 'AlgoEd25519
    -> Signer 'AlgoEd25519 V6Sig
mkEd25519SignerV6 = Ed25519Signer

mkEd448SignerV4
    :: VersionedPKPayload 'V4
    -> SecretKeyFor 'AlgoEd448
    -> Signer 'AlgoEd448 V4Sig
mkEd448SignerV4 = Ed448Signer

mkEd448SignerV6
    :: VersionedPKPayload 'V6
    -> SecretKeyFor 'AlgoEd448
    -> Signer 'AlgoEd448 V6Sig
mkEd448SignerV6 = Ed448Signer

data MessageError
    = MessageEncryptFailureError MessageEncryptFailure
    | MessageDecryptError String
    | MessageSignError SignError
    | MessageParseError String
    | MessageParseFailureError MessageParseFailure
    | MessageDecryptFailureError MessageDecryptFailure
    deriving (Eq, Show)

messageStep
    :: Monad m => Either MessageError a -> ExceptT MessageError m a
messageStep = ExceptT . pure

runMessageFlow
    :: ExceptT MessageError Identity a -> Either MessageError a
runMessageFlow = runIdentity . runExceptT

type MessageFlowT m = ExceptT MessageError m

runMessageFlowT :: MessageFlowT m a -> m (Either MessageError a)
runMessageFlowT = runExceptT

signStepT :: Monad m => Either SignError a -> MessageFlowT m a
signStepT = ExceptT . pure . first MessageSignError

parseStep
    :: Monad m
    => Either MessageParseFailure a -> ExceptT MessageError m a
parseStep = messageStep . first MessageParseFailureError

decryptStep
    :: Monad m
    => Either MessageDecryptFailure a -> ExceptT MessageError m a
decryptStep = messageStep . first MessageDecryptFailureError

encryptStep
    :: Monad m
    => Either MessageEncryptFailure a -> ExceptT MessageError m a
encryptStep = messageStep . first MessageEncryptFailureError

data MessageParseFailure
    = MissingEncryptedMessage
    | ExpectedSKESKThenEncryptedData
    | SKESKSEIPDAlgorithmMismatch
    | UnsupportedEncryptedSKESK
    | MissingLiteralDataPacket
    | UnknownCriticalPacketType Word8
    | BrokenCriticalPacketType Word8 String
    deriving (Eq, Show)

data AEADFailure
    = AEADChunkAuthFailed AEADAlgorithm Int
    | AEADFinalTagFailed AEADAlgorithm
    | AEADInitFailed CipherError
    deriving (Eq, Show)

data PayloadDecryptFailure
    = PayloadDecryptCipherFailed CipherError
    | PayloadDecryptMDCFailed MDCFailure
    | PayloadDecryptAEADFailed AEADFailure
    | PayloadDecryptSEIPDv2Failed SEIPDv2Failure
    | PayloadDecryptGeneric String
    deriving (Eq, Show)

data MessageDecryptFailure
    = SessionMaterialDerivationFailed S2KError
    | PayloadDecryptFailed PayloadDecryptFailure
    deriving (Eq, Show)

data MessageEncryptFailure
    = MessageEncryptSEIPDv2Failed SEIPDv2Failure
    | MessageEncryptCipherFailed CipherError
    | MessageEncryptS2KFailed S2KError
    | MessageEncryptDeprecatedS2KHash HashAlgorithm
    | MessageEncryptUnsupportedSymmetricAlgorithm SymmetricAlgorithm
    deriving (Eq, Show)

data ParsedEncryptedPayloadKind
    = LegacySEDPayloadKind
    | LegacySEIPDv1PayloadKind
    | SEIPDv2PayloadKind

data EncryptedPreludeKind
    = LegacyEncryptedPreludeKind
    | SEIPDv2EncryptedPreludeKind

data SessionMaterialExposure
    = DoNotExposeSessionMaterial
    | ExposeSessionMaterial
    deriving (Eq, Show)

data EncryptMessageProfile
    = RFC4880Message
    | RFC9580Message

data EncryptMessageOptions (p :: EncryptMessageProfile) where
    RFC4880EncryptMessageOptions
        :: { rfc4880EncryptMessageExposure :: SessionMaterialExposure
           , rfc4880EncryptMessageSymmetricAlgorithm :: SymmetricAlgorithm
           , rfc4880EncryptMessageS2K :: S2K
           , rfc4880EncryptMessageIV :: IV
           }
        -> EncryptMessageOptions 'RFC4880Message
    RFC9580EncryptMessageOptions
        :: { rfc9580EncryptMessageExposure :: SessionMaterialExposure
           , rfc9580EncryptMessageSymmetricAlgorithm :: SymmetricAlgorithm
           , rfc9580EncryptMessageS2K :: S2K
           , rfc9580EncryptMessageIV :: IV
           }
        -> EncryptMessageOptions 'RFC9580Message

deriving instance Eq (EncryptMessageOptions p)
deriving instance Show (EncryptMessageOptions p)

data RecoveredSessionMaterial
    = RecoveredSessionMaterial
    { recoveredSessionAlgorithm :: SymmetricAlgorithm
    , recoveredSessionKey :: SessionKey
    }
    deriving (Eq, Show)

renderMessageParseFailure :: MessageParseFailure -> String
renderMessageParseFailure MissingEncryptedMessage =
    "Could not parse encrypted OpenPGP message"
renderMessageParseFailure ExpectedSKESKThenEncryptedData =
    "Expected an SKESK packet followed by symmetrically encrypted data or SEIPD v2 data"
renderMessageParseFailure SKESKSEIPDAlgorithmMismatch =
    "SKESK and SEIPD v2 algorithms do not match"
renderMessageParseFailure UnsupportedEncryptedSKESK =
    "Cannot decrypt SKESK packets with encrypted session keys"
renderMessageParseFailure MissingLiteralDataPacket =
    "Decrypted message does not contain a literal data packet"
renderMessageParseFailure (UnknownCriticalPacketType t) =
    "Unknown critical packet type: " ++ show t
renderMessageParseFailure (BrokenCriticalPacketType t err) =
    "Broken critical packet type " ++ show t ++ ": " ++ err

renderMessageDecryptFailure :: MessageDecryptFailure -> String
renderMessageDecryptFailure (SessionMaterialDerivationFailed err) = renderS2KError err
renderMessageDecryptFailure (PayloadDecryptFailed err) = renderPayloadDecryptFailure err

renderMessageEncryptFailure :: MessageEncryptFailure -> String
renderMessageEncryptFailure (MessageEncryptSEIPDv2Failed err) = renderSEIPDv2Failure err
renderMessageEncryptFailure (MessageEncryptCipherFailed err) = renderCipherError err
renderMessageEncryptFailure (MessageEncryptS2KFailed err) = renderS2KError err
renderMessageEncryptFailure (MessageEncryptDeprecatedS2KHash ha) =
    "deprecated hash algorithm disallowed for modern message generation: "
        ++ show ha
renderMessageEncryptFailure (MessageEncryptUnsupportedSymmetricAlgorithm sa) =
    "symmetric algorithm disallowed for RFC9580 message generation: "
        ++ show sa

renderAEADFailure :: AEADFailure -> String
renderAEADFailure (AEADChunkAuthFailed algo chunk) =
    "AEAD chunk authentication failed for "
        ++ show algo
        ++ " at chunk "
        ++ show chunk
renderAEADFailure (AEADFinalTagFailed algo) =
    "AEAD final tag verification failed for " ++ show algo
renderAEADFailure (AEADInitFailed err) =
    "AEAD initialization failed: " ++ renderCipherError err

renderPayloadDecryptFailure :: PayloadDecryptFailure -> String
renderPayloadDecryptFailure (PayloadDecryptCipherFailed err) = renderCipherError err
renderPayloadDecryptFailure (PayloadDecryptMDCFailed err) = renderMDCFailure err
renderPayloadDecryptFailure (PayloadDecryptAEADFailed err) = renderAEADFailure err
renderPayloadDecryptFailure (PayloadDecryptSEIPDv2Failed err) = renderSEIPDv2Failure err
renderPayloadDecryptFailure (PayloadDecryptGeneric err) = err

renderMessageError :: MessageError -> String
renderMessageError (MessageEncryptFailureError err) = renderMessageEncryptFailure err
renderMessageError (MessageDecryptError err) = err
renderMessageError (MessageSignError err) = renderSignError err
renderMessageError (MessageParseError err) = err
renderMessageError (MessageParseFailureError err) = renderMessageParseFailure err
renderMessageError (MessageDecryptFailureError err) = renderMessageDecryptFailure err

mkEncryptedPayload :: BL.ByteString -> EncryptedPayload
mkEncryptedPayload = EncryptedPayload

mkClearPayload :: BL.ByteString -> ClearPayload
mkClearPayload = ClearPayload

clearPayloadBytes :: ClearPayload -> BL.ByteString
clearPayloadBytes = unClearPayload

encryptedPayloadBytes :: EncryptedPayload -> BL.ByteString
encryptedPayloadBytes = unEncryptedPayload

signBackendStep :: Either String a -> Either SignError a
signBackendStep = first SignBackendError

decryptSessionStep
    :: Either S2KError a -> Either MessageDecryptFailure a
decryptSessionStep = first SessionMaterialDerivationFailed

decryptSessionKeySizeStep
    :: Either CipherError a -> Either MessageDecryptFailure a
decryptSessionKeySizeStep =
    first
        (SessionMaterialDerivationFailed . S2KUnsupportedAlgorithm)

decryptCipherStep
    :: Either CipherError a -> Either MessageDecryptFailure a
decryptCipherStep = first (PayloadDecryptFailed . PayloadDecryptCipherFailed)

decryptMDCStep
    :: Either MDCFailure a -> Either MessageDecryptFailure a
decryptMDCStep = first (PayloadDecryptFailed . PayloadDecryptMDCFailed)

decryptSEIPDv2Step
    :: Either SEIPDv2Failure a -> Either MessageDecryptFailure a
decryptSEIPDv2Step = first (PayloadDecryptFailed . PayloadDecryptSEIPDv2Failed)

encryptMessage
    :: EncryptMessageOptions p
    -> Passphrase
    -> ClearPayload
    -> Either
        MessageError
        (EncryptedPayload, Maybe RecoveredSessionMaterial)
encryptMessage options passphrase payload = runMessageFlow $
    case options of
        RFC4880EncryptMessageOptions exposure sa s2k iv -> do
            encryptedPayload <-
                encryptMessageWithRFC4880Fallback sa s2k iv passphrase payload
            sessionKeyMaterial <- deriveSessionMaterial sa s2k passphrase
            pure
                ( encryptedPayload
                , exposedSessionMaterial exposure sa sessionKeyMaterial
                )
        RFC9580EncryptMessageOptions exposure sa s2k iv -> do
            encryptStep $ validateRFC9580MessageSymmetric defaultPolicy sa
            encryptStep $ validateModernMessageS2K defaultPolicy s2k
            encrypted <-
                encryptStep . first MessageEncryptSEIPDv2Failed $
                    encryptSEIPDv2WithSKESKBlock
                        sa
                        (messageDefaultAEADAlgorithm messagePolicy)
                        (messageDefaultChunkSize messagePolicy)
                        ( defaultSEIPDv2SaltFromIV
                            (messageSEIPDv2SaltOctets messagePolicy)
                            iv
                        )
                        s2k
                        (unPassphrase passphrase)
                        ( Block
                            [LiteralDataPkt BinaryData BL.empty 0 (unClearPayload payload)]
                        )
            sessionKeyMaterial <- deriveSessionMaterial sa s2k passphrase
            pure
                ( EncryptedPayload (runPut (put (Block encrypted)))
                , exposedSessionMaterial exposure sa sessionKeyMaterial
                )
  where
    messagePolicy = policyMessageEncryption defaultPolicy

defaultSEIPDv2SaltFromIV :: Int -> IV -> Salt
defaultSEIPDv2SaltFromIV outputLen (IV ivBytes) =
    Salt (B.take outputLen (B.concat (replicate outputLen seed)))
  where
    seed
        | B.null ivBytes = B.singleton 0
        | otherwise = ivBytes

encryptMessageWithRFC4880Fallback
    :: Monad m
    => SymmetricAlgorithm
    -> S2K
    -> IV
    -> Passphrase
    -> ClearPayload
    -> ExceptT MessageError m EncryptedPayload
encryptMessageWithRFC4880Fallback sa s2k iv passphrase payload = do
    keyLen <-
        encryptStep . first MessageEncryptCipherFailed $ keySize sa
    sessionMaterial <-
        encryptStep . first MessageEncryptS2KFailed $
            WrappedSessionMaterial
                <$> string2Key s2k keyLen (unPassphrase passphrase)
    let literal =
            LiteralDataPkt BinaryData BL.empty 0 (unClearPayload payload)
        cleartext = BL.toStrict (runPut (put (Block [literal])))
        cleartextWithMDC = cleartext <> mdcTrailerForSEIPDv1 iv cleartext
    encrypted <-
        encryptStep . first MessageEncryptCipherFailed $
            encryptOpenPGPCfbRaw
                OpenPGPCFBNoResyncW
                sa
                iv
                cleartextWithMDC
                (unWrappedSessionMaterial sessionMaterial)
    return . EncryptedPayload . runPut . put $
        Block
            [ SKESKPkt (SKESKPayloadV4Packet (SKESKPayloadV4 sa s2k Nothing))
            , SymEncIntegrityProtectedDataPkt
                (SEIPD1 1 (BL.fromStrict encrypted))
            ]

deriveSessionMaterial
    :: Monad m
    => SymmetricAlgorithm
    -> S2K
    -> Passphrase
    -> ExceptT MessageError m B.ByteString
deriveSessionMaterial sa s2k passphrase = do
    keyLen <-
        encryptStep . first MessageEncryptCipherFailed $ keySize sa
    encryptStep . first MessageEncryptS2KFailed $
        string2Key s2k keyLen (unPassphrase passphrase)

exposedSessionMaterial
    :: SessionMaterialExposure
    -> SymmetricAlgorithm
    -> B.ByteString
    -> Maybe RecoveredSessionMaterial
exposedSessionMaterial DoNotExposeSessionMaterial _ _ = Nothing
exposedSessionMaterial ExposeSessionMaterial sa sessionKeyMaterial =
    Just
        ( RecoveredSessionMaterial
            { recoveredSessionAlgorithm = sa
            , recoveredSessionKey = SessionKey sessionKeyMaterial
            }
        )

decryptMessage
    :: Passphrase
    -> EncryptedPayload
    -> Either MessageError ClearPayload
decryptMessage passphrase encrypted = runMessageFlow $ do
    encryptedPackets <-
        parseStep $
            rejectUnknownCriticalPacketsTyped
                (parsePkts (unEncryptedPayload encrypted))
    payload <-
        parseStep $ extractEncryptedPayload encryptedPackets
    cleartext <-
        decryptStep $ decryptPayload passphrase payload
    clearPackets <-
        parseStep $
            rejectUnknownCriticalPacketsTyped
                (parsePkts (unClearPayload cleartext))
    parseStep $ extractLiteralPayload clearPackets

signMessageWith
    :: (MonadRandom m, SigningCapability alg v)
    => Signer alg v
    -> ClearPayload
    -> m (Either MessageError BL.ByteString)
signMessageWith signer payload =
    runMessageFlowT $
        let applySubs builder hashed unhashed =
                addUnhashedSubs
                    (listToUnhashedSubs unhashed)
                    (addHashedSubs (listToHashedSubs hashed) builder)
         in case signer of
                RSASigner signerPK signingKey ->
                    case signerPK of
                        VersionedPKPayloadV4 pk ->
                            signV4Message
                                pk
                                ( \hashed unhashed clear ->
                                    signDataWithRSABuilder
                                        ( applySubs
                                            (sigBuilderInitTyped @'PKA.RSA RFC9580W BinarySig SHA512W)
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload
                        VersionedPKPayloadV6 pk ->
                            signV6Message
                                pk
                                ( \salt hashed unhashed clear ->
                                    signDataWithRSAV6Builder
                                        ( applySubs
                                            (sigBuilderInitV6Typed @'PKA.RSA RFC9580W BinarySig SHA512W salt)
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload
                Ed25519Signer signerPK signingKey ->
                    case signerPK of
                        VersionedPKPayloadV4 pk ->
                            signV4Message
                                pk
                                ( \hashed unhashed clear ->
                                    signDataWithEd25519Builder
                                        ( applySubs
                                            (sigBuilderInitTyped @'PKA.Ed25519 RFC9580W BinarySig SHA512W)
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload
                        VersionedPKPayloadV6 pk ->
                            signV6Message
                                pk
                                ( \salt hashed unhashed clear ->
                                    signDataWithEd25519V6Builder
                                        ( applySubs
                                            ( sigBuilderInitV6Typed @'PKA.Ed25519
                                                RFC9580W
                                                BinarySig
                                                SHA512W
                                                salt
                                            )
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload
                Ed448Signer signerPK signingKey ->
                    case signerPK of
                        VersionedPKPayloadV4 pk ->
                            signV4Message
                                pk
                                ( \hashed unhashed clear ->
                                    signDataWithEd448Builder
                                        ( applySubs
                                            (sigBuilderInitTyped @'PKA.Ed448 RFC9580W BinarySig SHA512W)
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload
                        VersionedPKPayloadV6 pk ->
                            signV6Message
                                pk
                                ( \salt hashed unhashed clear ->
                                    signDataWithEd448V6Builder
                                        ( applySubs
                                            (sigBuilderInitV6Typed @'PKA.Ed448 RFC9580W BinarySig SHA512W salt)
                                            hashed
                                            unhashed
                                        )
                                        signingKey
                                        clear
                                )
                                payload

signMessage
    :: (MonadRandom m, SigningCapability alg v)
    => Signer alg v
    -> BL.ByteString
    -> m (Either MessageError BL.ByteString)
signMessage signer = signMessageWith signer . mkClearPayload

verifySignedMessage
    :: ConduitMessage.VerificationOptions
    -> PublicKeyring
    -> BL.ByteString
    -> [Either VerificationError Verification]
verifySignedMessage = ConduitMessage.verifyMessage

signV4Message
    :: Monad m
    => PKPayload 'V4
    -> ( [SigSubPacket]
         -> [SigSubPacket]
         -> BL.ByteString
         -> Either SignError SignaturePayload
       )
    -> ClearPayload
    -> MessageFlowT m BL.ByteString
signV4Message signer signingFn payload =
    signStepT (signV4WithIssuers signer signingFn payload)

signV6Message
    :: MonadRandom m
    => PKPayload 'V6
    -> ( SignatureSalt
         -> [SigSubPacket]
         -> [SigSubPacket]
         -> BL.ByteString
         -> Either SignError SignaturePayload
       )
    -> ClearPayload
    -> MessageFlowT m BL.ByteString
signV6Message signer signingFn payload = do
    salt <- lift randomSHA512SignatureSalt
    signStepT
        (signV6WithFingerprintOnly signer (signingFn salt) payload)

randomSHA512SignatureSalt :: MonadRandom m => m SignatureSalt
randomSHA512SignatureSalt =
    SignatureSalt . BL.fromStrict <$> getRandomBytes 32

signV4WithIssuers
    :: PKPayload 'V4
    -> ( [SigSubPacket]
         -> [SigSubPacket]
         -> BL.ByteString
         -> Either SignError SignaturePayload
       )
    -> ClearPayload
    -> Either SignError BL.ByteString
signV4WithIssuers signer signingFn payload = do
    issuerKeyId <-
        signBackendStep (eightOctetKeyID (SomePKPayload signer))
    let hashed =
            [ SigSubPacket
                False
                ( IssuerFingerprint
                    IssuerFingerprintV4
                    (fingerprint (SomePKPayload signer))
                )
            ]
        unhashed = [SigSubPacket False (Issuer issuerKeyId)]
    signWithSubpackets hashed unhashed signingFn payload

signV6WithFingerprintOnly
    :: PKPayload 'V6
    -> ( [SigSubPacket]
         -> [SigSubPacket]
         -> BL.ByteString
         -> Either SignError SignaturePayload
       )
    -> ClearPayload
    -> Either SignError BL.ByteString
signV6WithFingerprintOnly signer signingFn payload = do
    let hashed =
            [ SigSubPacket
                False
                ( IssuerFingerprint
                    IssuerFingerprintV6
                    (fingerprint (SomePKPayload signer))
                )
            ]
        unhashed = []
    signWithSubpackets hashed unhashed signingFn payload

signWithSubpackets
    :: [SigSubPacket]
    -> [SigSubPacket]
    -> ( [SigSubPacket]
         -> [SigSubPacket]
         -> BL.ByteString
         -> Either SignError SignaturePayload
       )
    -> ClearPayload
    -> Either SignError BL.ByteString
signWithSubpackets hashed unhashed signingFn payload = do
    let clear = unClearPayload payload
        literal = LiteralDataPkt BinaryData BL.empty 0 clear
    signature <- signingFn hashed unhashed clear
    let sigPkt = SignaturePkt signature
    bimap
        (SignBackendError . renderOPSBuildError)
        ( \ops ->
            runPut . put $ Block [OnePassSignaturePkt ops, literal, sigPkt]
        )
        (buildOnePassSignature False signature)

extractEncryptedPayload
    :: [Pkt] -> Either MessageParseFailure SomeParsedEncryptedPayload
extractEncryptedPayload =
    fmap parsedEncryptedPayloadFromPrelude
        . extractEncryptedPreludeTyped

data EncryptedPrelude (k :: EncryptedPreludeKind) where
    LegacySEDPrelude
        :: SKESK 'SKESKV4
        -> B.ByteString
        -> EncryptedPrelude 'LegacyEncryptedPreludeKind
    LegacySEIPDv1Prelude
        :: SKESK 'SKESKV4
        -> B.ByteString
        -> EncryptedPrelude 'LegacyEncryptedPreludeKind
    SEIPDv2SKESK4Prelude
        :: SymmetricAlgorithm
        -> S2K
        -> AEADAlgorithm
        -> Word8
        -> Salt
        -> B.ByteString
        -> EncryptedPrelude 'SEIPDv2EncryptedPreludeKind
    SEIPDv2SKESK6Prelude
        :: SymmetricAlgorithm
        -> AEADAlgorithm
        -> S2K
        -> BL.ByteString
        -> BL.ByteString
        -> BL.ByteString
        -> Word8
        -> Salt
        -> B.ByteString
        -> EncryptedPrelude 'SEIPDv2EncryptedPreludeKind

data SomeEncryptedPrelude where
    SomeEncryptedPrelude
        :: EncryptedPrelude k -> SomeEncryptedPrelude

extractEncryptedPreludeTyped
    :: [Pkt] -> Either MessageParseFailure SomeEncryptedPrelude
extractEncryptedPreludeTyped
    ( SKESKPkt (SKESKPayloadV4Packet (SKESKPayloadV4 sa s2k esk))
            : SymEncDataPkt payload
            : _
        ) =
        Right
            ( SomeEncryptedPrelude
                ( LegacySEDPrelude
                    (SKESK4Packet sa s2k esk)
                    (BL.toStrict payload)
                )
            )
extractEncryptedPreludeTyped
    ( SKESKPkt (SKESKPayloadV4Packet (SKESKPayloadV4 sa s2k esk))
            : SymEncIntegrityProtectedDataPkt (SEIPD1 _ payload)
            : _
        ) =
        Right
            ( SomeEncryptedPrelude
                ( LegacySEIPDv1Prelude
                    (SKESK4Packet sa s2k esk)
                    (BL.toStrict payload)
                )
            )
extractEncryptedPreludeTyped
    ( SKESKPkt skesk
            : SymEncIntegrityProtectedDataPkt
                (SEIPD2 payloadSA aead chunkSize salt payload)
            : _
        ) =
        toSEIPDv2Prelude skesk payloadSA aead chunkSize salt payload
extractEncryptedPreludeTyped [] = Left MissingEncryptedMessage
extractEncryptedPreludeTyped _ = Left ExpectedSKESKThenEncryptedData

toSEIPDv2Prelude
    :: SKESKPayload
    -> SymmetricAlgorithm
    -> AEADAlgorithm
    -> Word8
    -> Salt
    -> BL.ByteString
    -> Either MessageParseFailure SomeEncryptedPrelude
toSEIPDv2Prelude (SKESKPayloadV4Packet (SKESKPayloadV4 sa s2k Nothing)) payloadSA aead chunkSize salt payload
    | sa /= payloadSA = Left SKESKSEIPDAlgorithmMismatch
    | otherwise =
        Right
            ( SomeEncryptedPrelude
                ( SEIPDv2SKESK4Prelude
                    sa
                    s2k
                    aead
                    chunkSize
                    salt
                    (BL.toStrict payload)
                )
            )
toSEIPDv2Prelude (SKESKPayloadV6Packet (SKESKPayloadV6 sa aa s2k iv esk tag)) payloadSA _aead chunkSize salt payload
    | sa /= payloadSA = Left SKESKSEIPDAlgorithmMismatch
    | otherwise =
        Right
            ( SomeEncryptedPrelude
                ( SEIPDv2SKESK6Prelude
                    sa
                    aa
                    s2k
                    iv
                    esk
                    tag
                    chunkSize
                    salt
                    (BL.toStrict payload)
                )
            )
toSEIPDv2Prelude (SKESKPayloadV4Packet (SKESKPayloadV4 _ _ (Just _))) _ _ _ _ _ = Left UnsupportedEncryptedSKESK

parsedEncryptedPayloadFromPrelude
    :: SomeEncryptedPrelude -> SomeParsedEncryptedPayload
parsedEncryptedPayloadFromPrelude (SomeEncryptedPrelude prelude) =
    case prelude of
        LegacySEDPrelude skesk payload ->
            SomeParsedEncryptedPayload (LegacySEDPayload skesk payload)
        LegacySEIPDv1Prelude skesk payload ->
            SomeParsedEncryptedPayload (LegacySEIPDv1Payload skesk payload)
        SEIPDv2SKESK4Prelude sa s2k aead chunkSize salt payload ->
            SomeParsedEncryptedPayload
                ( SEIPDv2Payload
                    sa
                    aead
                    chunkSize
                    salt
                    (SEIPDv2SKESK4 sa s2k)
                    payload
                )
        SEIPDv2SKESK6Prelude sa aa s2k iv esk tag chunkSize salt payload ->
            SomeParsedEncryptedPayload
                ( SEIPDv2Payload
                    sa
                    aa
                    chunkSize
                    salt
                    (SEIPDv2SKESK6 sa aa s2k iv esk tag)
                    payload
                )

data SEIPDv2SKESKInfo (v :: KeyVersion) where
    SEIPDv2SKESK4
        :: SymmetricAlgorithm -> S2K -> SEIPDv2SKESKInfo 'V4
    SEIPDv2SKESK6
        :: SymmetricAlgorithm
        -> AEADAlgorithm
        -> S2K
        -> BL.ByteString
        -> BL.ByteString
        -> BL.ByteString
        -> SEIPDv2SKESKInfo 'V6

data ParsedEncryptedPayload (k :: ParsedEncryptedPayloadKind) where
    LegacySEDPayload
        :: SKESK 'SKESKV4
        -> B.ByteString
        -> ParsedEncryptedPayload 'LegacySEDPayloadKind
    LegacySEIPDv1Payload
        :: SKESK 'SKESKV4
        -> B.ByteString
        -> ParsedEncryptedPayload 'LegacySEIPDv1PayloadKind
    SEIPDv2Payload
        :: SymmetricAlgorithm
        -> AEADAlgorithm
        -> Word8
        -> Salt
        -> SEIPDv2SKESKInfo v
        -> B.ByteString
        -> ParsedEncryptedPayload 'SEIPDv2PayloadKind

data SomeParsedEncryptedPayload where
    SomeParsedEncryptedPayload
        :: ParsedEncryptedPayload k
        -> SomeParsedEncryptedPayload

decryptPayload
    :: Passphrase
    -> SomeParsedEncryptedPayload
    -> Either MessageDecryptFailure ClearPayload
decryptPayload passphrase (SomeParsedEncryptedPayload payload) =
    case payload of
        LegacySEDPayload skesk encryptedPayload ->
            decryptLegacySEDPayloadTyped passphrase skesk encryptedPayload
        LegacySEIPDv1Payload skesk encryptedPayload ->
            decryptLegacySEIPDv1PayloadTyped
                passphrase
                skesk
                encryptedPayload
        SEIPDv2Payload sa aead chunkSize salt skeskInfo encryptedPayload ->
            decryptSEIPDv2PayloadTyped
                passphrase
                sa
                aead
                chunkSize
                salt
                skeskInfo
                encryptedPayload

decryptLegacySEDPayloadTyped
    :: Passphrase
    -> SKESK 'SKESKV4
    -> B.ByteString
    -> Either MessageDecryptFailure ClearPayload
decryptLegacySEDPayloadTyped passphrase skesk payload = do
    (sessionAlgorithm, sessionKeyBytes) <-
        decryptSessionStep $
            skesk2SessionKey skesk (unPassphrase passphrase)
    decryptCipherStep $
        ClearPayload . BL.fromStrict
            <$> decryptOpenPGPCfb
                sessionAlgorithm
                payload
                sessionKeyBytes

decryptLegacySEIPDv1PayloadTyped
    :: Passphrase
    -> SKESK 'SKESKV4
    -> B.ByteString
    -> Either MessageDecryptFailure ClearPayload
decryptLegacySEIPDv1PayloadTyped passphrase skesk payload = do
    (sessionAlgorithm, sessionKeyBytes) <-
        decryptSessionStep $
            skesk2SessionKey skesk (unPassphrase passphrase)
    (nonce, decrypted) <-
        decryptCipherStep $
            decryptPreservingNonce sessionAlgorithm payload sessionKeyBytes
    cleartext <-
        decryptMDCStep $ validateSEIPD1MDC nonce decrypted
    Right (ClearPayload (BL.fromStrict cleartext))

decryptSEIPDv2PayloadTyped
    :: Passphrase
    -> SymmetricAlgorithm
    -> AEADAlgorithm
    -> Word8
    -> Salt
    -> SEIPDv2SKESKInfo v
    -> B.ByteString
    -> Either MessageDecryptFailure ClearPayload
decryptSEIPDv2PayloadTyped passphrase sa aead chunkSize salt skeskInfo payload = do
    sessionKey <-
        SessionKey <$> deriveSEIPDv2SessionKeyBytes passphrase skeskInfo
    decryptSEIPDv2Step $
        ClearPayload . BL.fromStrict
            <$> decryptSEIPDv2Payload sa aead chunkSize salt payload sessionKey

deriveSEIPDv2SessionKeyBytes
    :: Passphrase
    -> SEIPDv2SKESKInfo v
    -> Either MessageDecryptFailure B.ByteString
deriveSEIPDv2SessionKeyBytes passphrase (SEIPDv2SKESK4 sa s2k) =
    deriveSessionKeyBytes passphrase sa s2k
deriveSEIPDv2SessionKeyBytes passphrase (SEIPDv2SKESK6 sa aead s2k iv esk tag) = do
    ikm <- deriveSessionKeyBytes passphrase sa s2k
    kek <- decryptSEIPDv2Step $ deriveSKESK6KEK sa aead ikm
    decryptSEIPDv2Step $
        decryptSKESK6SessionKey
            sa
            aead
            kek
            (BL.toStrict iv)
            (BL.toStrict esk)
            (BL.toStrict tag)

deriveSessionKeyBytes
    :: Passphrase
    -> SymmetricAlgorithm
    -> S2K
    -> Either MessageDecryptFailure B.ByteString
deriveSessionKeyBytes passphrase sa s2k = do
    keyLen <- decryptSessionKeySizeStep $ keySize sa
    decryptSessionStep $
        string2Key s2k keyLen (unPassphrase passphrase)

extractLiteralPayload
    :: [Pkt] -> Either MessageParseFailure ClearPayload
extractLiteralPayload pkts =
    case [p | LiteralDataPkt _ _ _ p <- pkts] of
        payload : _ -> Right (ClearPayload payload)
        [] -> Left MissingLiteralDataPacket

rejectUnknownCriticalPacketsTyped
    :: [Pkt] -> Either MessageParseFailure [Pkt]
rejectUnknownCriticalPacketsTyped =
    fmap reverse . foldM go []
  where
    go acc pkt =
        case pkt of
            OtherPacketPkt t _ | t < 40 -> Left (UnknownCriticalPacketType t)
            BrokenPacketPkt err t _ | t < 40 -> Left (BrokenCriticalPacketType t err)
            _ -> Right (pkt : acc)

validateModernMessageS2K
    :: OpenPGPPolicy -> S2K -> Either MessageEncryptFailure ()
validateModernMessageS2K policy s2k =
    case s2kHashAlgorithm s2k of
        Just ha
            | ha
                `elem` deprecatedHashAlgorithms (policyGenerationDeprecations policy) ->
                Left (MessageEncryptDeprecatedS2KHash ha)
        _ -> Right ()

validateRFC9580MessageSymmetric
    :: OpenPGPPolicy
    -> SymmetricAlgorithm
    -> Either MessageEncryptFailure ()
validateRFC9580MessageSymmetric policy sa
    | supportsSEIPDv2Symmetric policy sa = Right ()
    | otherwise =
        Left
            (MessageEncryptUnsupportedSymmetricAlgorithm sa)

s2kHashAlgorithm :: S2K -> Maybe HashAlgorithm
s2kHashAlgorithm (Simple ha) = Just ha
s2kHashAlgorithm (Salted ha _) = Just ha
s2kHashAlgorithm (IteratedSalted ha _ _) = Just ha
s2kHashAlgorithm Argon2 {} = Nothing
s2kHashAlgorithm (OtherS2K _ _) = Nothing