packages feed

hOpenPGP-3.5.1: Codec/Encryption/OpenPGP/Signing.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

{- | High-level signing monad transformer for OpenPGP.

'SigningT' wraps a 'TK' 'SecretTK' and provides a
unified interface for creating signatures with any
signing-capable subkey or the primary key.

It handles key selection, payload construction
and signature generation.
-}
module Codec.Encryption.OpenPGP.Signing
    ( -- * SigningT transformer
      SigningT
    , runSigningT
    , SigningError (..)
    , renderSigningError

      -- * Signing target selection
    , SigningTarget (..)
    , AvailableSigner (..)
    , asKeyId
    , asFingerprint
    , asKeyPacket
    , asSKey
    , asIsPrimary
    , asUsage
    , listAvailableSigners
    , filterSigningCapable
    , filterByKeyId
    , filterByFingerprint

      -- * Signing payloads
    , SigningPayload (..)

      -- * Low-level signing
    , signWith

      -- * High-level signing operations
    , signUserId
    , signUat

      -- * Timestamp control
    , getCurrentTimestamp
    , setCurrentTimestamp
    , withTimestamp
    ) where

import Control.Monad (guard)
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (ExceptT (..), runExceptT)
import Control.Monad.Trans.RWS
    ( RWST (..)
    , ask
    , get
    , put
    , runRWST
    )
import Crypto.Error (eitherCryptoError)
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 (first)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BL
import Data.List (find)
import Data.Maybe (listToMaybe)
import qualified Data.Set as Set
import Data.Text (Text)

import Codec.Encryption.OpenPGP.Fingerprint
    ( eightOctetKeyID
    , fingerprint
    )
import Codec.Encryption.OpenPGP.SignatureQualities
    ( signatureHashedSubpacketsKnown
    )
import Codec.Encryption.OpenPGP.Signatures
    ( SignError (..)
    , payloadForCertRevocation
    , payloadForDirectKey
    , payloadForPrimaryKeyBinding
    , payloadForSubkeyBinding
    , payloadForSubkeyRevocation
    , payloadForUat
    , payloadForUserId
    , randomSignatureSalt
    , renderSignError
    , signCertRevocation
    , signDataWithEd25519
    , signDataWithEd25519V6
    , signDataWithEd448
    , signDataWithEd448V6
    , signDataWithRSA
    , signDataWithRSAV6
    , signDirectKey
    , signSubkeyBinding
    , signSubkeyRevocation
    )
import qualified Codec.Encryption.OpenPGP.Signatures as S
import Codec.Encryption.OpenPGP.Subpackets
    ( TextNormalizationMode (..)
    )
import Codec.Encryption.OpenPGP.Types
import qualified Codec.Encryption.OpenPGP.Types.Internal.Base as PKA
import Codec.Encryption.OpenPGP.Types.Internal.CryptonNewtypes
    ( RSA_PrivateKey (..)
    )
import Codec.Encryption.OpenPGP.Types.Internal.TK
    ( TK (..)
    , TKKind (..)
    , _tkPrimaryKey
    , _tkSubs
    )

-- | Signing-specific errors.
data SigningError
    = SigningSignError !SignError
    | SigningNoSignersAvailable
    | SigningInvalidTarget !ByteString
    | SigningKeyNotSigningCapable !ByteString
    | SigningKeyExpired !ByteString
    | SigningKeyNotYetValid !ByteString
    | SigningSKeyInitFailed !String
    deriving (Eq, Show)

renderSigningError :: SigningError -> String
renderSigningError (SigningSignError e) = renderSignError e
renderSigningError SigningNoSignersAvailable = "no signing-capable keys available"
renderSigningError (SigningInvalidTarget kid) = "invalid signing target: " ++ show kid
renderSigningError (SigningKeyNotSigningCapable kid) = "key is not signing-capable: " ++ show kid
renderSigningError (SigningKeyExpired kid) = "key has expired: " ++ show kid
renderSigningError (SigningKeyNotYetValid kid) = "key is not yet valid: " ++ show kid
renderSigningError (SigningSKeyInitFailed msg) = "failed to initialize secret key: " ++ msg

-- | The signing monad transformer.
newtype SigningT (tk :: TKKind) m a = SigningT
    { unSigningT
        :: ExceptT
            SigningError
            ( RWST
                (TK 'SecretTK)
                [Text]
                ThirtyTwoBitTimeStamp
                m
            )
            a
    }
    deriving newtype (Applicative, Functor, Monad)

-- | Run a 'SigningT' action.
runSigningT
    :: Monad m
    => TK 'SecretTK
    -- ^ The secret transferable key to sign with
    -> ThirtyTwoBitTimeStamp
    -- ^ Initial timestamp
    -> SigningT 'SecretTK m a
    -- ^ Action to run
    -> m (Either SigningError a)
runSigningT tk ts (SigningT action) =
    (runRWST (runExceptT action) tk) ts >>= \(e, _, _) -> return e

-- | Which key to sign with.
data SigningTarget
    = SignWithPrimary
    | SignWithKey !PKA.EightOctetKeyId
    | SignWithBest
    | SignWithBestFilter !(AvailableSigner -> Bool)

-- | A signing-capable key extracted from a 'TK'.
data AvailableSigner = AvailableSigner
    { asKeyId :: !PKA.EightOctetKeyId
    , asFingerprint :: !PKA.Fingerprint
    , asKeyPacket :: !(KeyPkt 'SecretPkt)
    , asSKey :: !SKey
    , asIsPrimary :: !Bool
    , asUsage :: !(Set.Set KeyFlag)
    }

signingCapableFlags :: Set.Set KeyFlag
signingCapableFlags = Set.fromList [SignDataKey, CertifyKeysKey, AuthKey]

isSigningCapable :: Set.Set KeyFlag -> Bool
isSigningCapable usage = not (Set.null (Set.intersection usage signingCapableFlags))

-- | List all signing-capable keys from a 'TK' 'SecretTK'.
listAvailableSigners :: TK 'SecretTK -> [AvailableSigner]
listAvailableSigners tk =
    primary : subs
  where
    primaryKp = _tkPrimaryKey tk
    primaryPkp = keyPktPKPayload primaryKp
    primarySka = secretKeyPktSKAddendum primaryKp
    primaryUsage =
        foldr
            Set.union
            Set.empty
            (map sigFlags (_tkDirectKeySigs tk ++ _tkRevs tk))
    primary =
        AvailableSigner
            { asKeyId = either error id (eightOctetKeyID primaryPkp)
            , asFingerprint = fingerprint primaryPkp
            , asKeyPacket = primaryKp
            , asSKey = case primarySka of
                SUSUnprotected sk _ -> sk
                _ -> error "encrypted secret key not supported in SigningT"
            , asIsPrimary = True
            , asUsage = primaryUsage
            }
    subs = do
        (subKp, subSigs) <- _tkSubs tk
        let subPkp = keyPktPKPayload subKp
            subSka = secretKeyPktSKAddendum subKp
            subUsage = foldr Set.union Set.empty (map sigFlags subSigs)
        guard (isSigningCapable subUsage)
        pure
            AvailableSigner
                { asKeyId = either error id (eightOctetKeyID subPkp)
                , asFingerprint = fingerprint subPkp
                , asKeyPacket = subKp
                , asSKey = case subSka of
                    SUSUnprotected sk _ -> sk
                    _ -> error "encrypted secret key not supported in SigningT"
                , asIsPrimary = False
                , asUsage = subUsage
                }
    sigFlags sig = case signatureHashedSubpacketsKnown sig of
        Nothing -> Set.empty
        Just hs -> foldr Set.union Set.empty (map goSub hs)
    goSub (SigSubPacket _ (KeyFlags flags)) = flags
    goSub _ = Set.empty

-- | A typed signing payload.
data SigningPayload
    = SPUserId !SigType !UserId
    | SPUat !SigType !UserAttribute
    | SPDirectKey
    | SPKeyRevocation
    | SPSubkeyRevocation !(KeyPkt 'PublicPkt)
    | SPCertRevocation !UserId
    | SPSignSubkeyBinding !(KeyPkt 'PublicPkt)
    | SPPrimaryKeyBinding !(KeyPkt 'PublicPkt)
    | SPRaw !SigType !ByteString
    deriving (Eq, Show)

-- | Sign an arbitrary payload with a selected key.
signWith
    :: forall m
     . MonadRandom m
    => SigningTarget
    -> SigningPayload
    -> SigningT 'SecretTK m (Either SigningError SignaturePayload)
signWith target payload = do
    tk <- SigningT . lift $ ask
    ts <- SigningT . lift $ get
    let signers = listAvailableSigners tk
        selected = resolveTarget target signers
    case selected of
        Nothing -> pure $ Left (signingErrorFromTarget target)
        Just signer -> do
            let kp = asKeyPacket signer
                ska = asSKey signer
            signWithPayload ts kp ska payload

signWithPayload
    :: (Monad m, MonadRandom m)
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> SKey
    -> SigningPayload
    -> SigningT 'SecretTK m (Either SigningError SignaturePayload)
signWithPayload ts kp ska payload = do
    result <- SigningT $ lift $ lift $ go ska
    pure $ first SigningSignError result
  where
    go _ska = case keyPktPKPayload kp of
        PKPayload V4 _ _ pka _ -> goV4 pka
        PKPayload V6 _ _ pka _ -> goV6 pka
        PKPayload DeprecatedV3 _ _ pka _ -> goV4 pka
    goV4 pka = case (pka, ska) of
        (RSA, RSAPrivateKey rsaPriv) -> signRSA ts kp (unRSA_PrivateKey rsaPriv) payload
        (EdDSALegacy, EdDSAPrivateKey EdSigningCurve25519 bs) ->
            signEd25519V4 ts kp bs payload
        (EdDSALegacy, Ed25519PrivateKey bs) ->
            signEd25519V4 ts kp bs payload
        (Ed448, EdDSAPrivateKey EdSigningCurve448 bs) ->
            signEd448V4 ts kp bs payload
        (Ed448, Ed448PrivateKey bs) ->
            signEd448V4 ts kp bs payload
        _ ->
            pure $
                Left (SignBackendError "unsupported signing key type for V4")
    goV6 pka = case (pka, ska) of
        (RSA, RSAPrivateKey rsaPriv) -> signRSA ts kp (unRSA_PrivateKey rsaPriv) payload
        (Ed25519, EdDSAPrivateKey EdSigningCurve25519 bs) ->
            signEd25519V6 ts kp bs payload
        (Ed25519, Ed25519PrivateKey bs) ->
            signEd25519V6 ts kp bs payload
        (Ed448, EdDSAPrivateKey EdSigningCurve448 bs) ->
            signEd448V6 ts kp bs payload
        (Ed448, Ed448PrivateKey bs) ->
            signEd448V6 ts kp bs payload
        _ ->
            pure $
                Left (SignBackendError "unsupported signing key type for V6")

signRSA
    :: MonadRandom m
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> RSATypes.PrivateKey
    -> SigningPayload
    -> m (Either SignError SignaturePayload)
signRSA ts kp p payload =
    case keyPktPKPayload kp of
        PKPayload V4 _ _ _ _ -> pure $ goV4 payload
        PKPayload V6 _ _ _ _ -> do
            salt <- randomSignatureSalt ha
            pure $ goV6 salt payload
        PKPayload DeprecatedV3 _ _ _ _ -> pure $ goV4 payload
  where
    ha = SHA512
    hashed = [SigSubPacket True (SigCreationTime ts)]
    unhashed = []
    goV4 = signPayloadWithRSA kp ha hashed unhashed p
    goV6 salt = signPayloadWithRSAV6 kp ha salt hashed unhashed p

signEd25519V4
    :: MonadRandom m
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> ByteString
    -> SigningPayload
    -> m (Either SignError SignaturePayload)
signEd25519V4 ts kp bs payload =
    case eitherCryptoError (Ed25519.secretKey bs) of
        Left err -> pure $ Left (SignBackendError (show err))
        Right sk ->
            let ha = SHA512
                hashed = [SigSubPacket True (SigCreationTime ts)]
                unhashed = []
             in pure $ signPayloadWithEd25519 kp ha hashed unhashed sk payload

signEd25519V6
    :: MonadRandom m
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> ByteString
    -> SigningPayload
    -> m (Either SignError SignaturePayload)
signEd25519V6 ts kp bs payload = do
    let ha = SHA512
    salt <- randomSignatureSalt ha
    case eitherCryptoError (Ed25519.secretKey bs) of
        Left err -> pure $ Left (SignBackendError (show err))
        Right sk ->
            let hashed = [SigSubPacket True (SigCreationTime ts)]
                unhashed = []
             in pure $
                    signPayloadWithEd25519V6 kp ha salt hashed unhashed sk payload

signEd448V4
    :: MonadRandom m
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> ByteString
    -> SigningPayload
    -> m (Either SignError SignaturePayload)
signEd448V4 ts kp bs payload =
    case eitherCryptoError (Ed448.secretKey bs) of
        Left err -> pure $ Left (SignBackendError (show err))
        Right sk ->
            let ha = SHA512
                hashed = [SigSubPacket True (SigCreationTime ts)]
                unhashed = []
             in pure $ signPayloadWithEd448 kp ha hashed unhashed sk payload

signEd448V6
    :: MonadRandom m
    => ThirtyTwoBitTimeStamp
    -> KeyPkt 'SecretPkt
    -> ByteString
    -> SigningPayload
    -> m (Either SignError SignaturePayload)
signEd448V6 ts kp bs payload = do
    let ha = SHA512
    salt <- randomSignatureSalt ha
    case eitherCryptoError (Ed448.secretKey bs) of
        Left err -> pure $ Left (SignBackendError (show err))
        Right sk ->
            let hashed = [SigSubPacket True (SigCreationTime ts)]
                unhashed = []
             in pure $
                    signPayloadWithEd448V6 kp ha salt hashed unhashed sk payload

signPayloadWithRSA
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> RSATypes.PrivateKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithRSA kp ha hs us p payload =
    case payload of
        SPUserId st uid -> signDataWithRSA ha st p hs us (payloadForUserId pkp uid)
        SPUat st uat -> signDataWithRSA ha st p hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithRSA
                ha
                DirectKeySignature
                p
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithRSA
                ha
                KeyRevocationSig
                p
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithRSA
                ha
                SubkeyRevocationSig
                p
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithRSA
                ha
                CertRevocationSig
                p
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithRSA
                ha
                SubkeyBindingSig
                p
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithRSA
                ha
                PrimaryKeyBindingSig
                p
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithRSA ha st p hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signPayloadWithRSAV6
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> SignatureSalt
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> RSATypes.PrivateKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithRSAV6 kp ha salt hs us p payload =
    case payload of
        SPUserId st uid ->
            signDataWithRSAV6 ha st salt p hs us (payloadForUserId pkp uid)
        SPUat st uat -> signDataWithRSAV6 ha st salt p hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithRSAV6
                ha
                DirectKeySignature
                salt
                p
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithRSAV6
                ha
                KeyRevocationSig
                salt
                p
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithRSAV6
                ha
                SubkeyRevocationSig
                salt
                p
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithRSAV6
                ha
                CertRevocationSig
                salt
                p
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithRSAV6
                ha
                SubkeyBindingSig
                salt
                p
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithRSAV6
                ha
                PrimaryKeyBindingSig
                salt
                p
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithRSAV6 ha st salt p hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signPayloadWithEd25519
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> Ed25519.SecretKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithEd25519 kp ha hs us sk payload =
    case payload of
        SPUserId st uid -> signDataWithEd25519 ha st sk hs us (payloadForUserId pkp uid)
        SPUat st uat -> signDataWithEd25519 ha st sk hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithEd25519
                ha
                DirectKeySignature
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithEd25519
                ha
                KeyRevocationSig
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithEd25519
                ha
                SubkeyRevocationSig
                sk
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithEd25519
                ha
                CertRevocationSig
                sk
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithEd25519
                ha
                SubkeyBindingSig
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithEd25519
                ha
                PrimaryKeyBindingSig
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithEd25519 ha st sk hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signPayloadWithEd25519V6
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> SignatureSalt
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> Ed25519.SecretKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithEd25519V6 kp ha salt hs us sk payload =
    case payload of
        SPUserId st uid ->
            signDataWithEd25519V6
                ha
                st
                salt
                sk
                hs
                us
                (payloadForUserId pkp uid)
        SPUat st uat ->
            signDataWithEd25519V6 ha st salt sk hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithEd25519V6
                ha
                DirectKeySignature
                salt
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithEd25519V6
                ha
                KeyRevocationSig
                salt
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithEd25519V6
                ha
                SubkeyRevocationSig
                salt
                sk
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithEd25519V6
                ha
                CertRevocationSig
                salt
                sk
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithEd25519V6
                ha
                SubkeyBindingSig
                salt
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithEd25519V6
                ha
                PrimaryKeyBindingSig
                salt
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithEd25519V6 ha st salt sk hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signPayloadWithEd448
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> Ed448.SecretKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithEd448 kp ha hs us sk payload =
    case payload of
        SPUserId st uid -> signDataWithEd448 ha st sk hs us (payloadForUserId pkp uid)
        SPUat st uat -> signDataWithEd448 ha st sk hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithEd448
                ha
                DirectKeySignature
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithEd448
                ha
                KeyRevocationSig
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithEd448
                ha
                SubkeyRevocationSig
                sk
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithEd448
                ha
                CertRevocationSig
                sk
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithEd448
                ha
                SubkeyBindingSig
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithEd448
                ha
                PrimaryKeyBindingSig
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithEd448 ha st sk hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signPayloadWithEd448V6
    :: KeyPkt 'SecretPkt
    -> HashAlgorithm
    -> SignatureSalt
    -> [SigSubPacket]
    -> [SigSubPacket]
    -> Ed448.SecretKey
    -> SigningPayload
    -> Either SignError SignaturePayload
signPayloadWithEd448V6 kp ha salt hs us sk payload =
    case payload of
        SPUserId st uid ->
            signDataWithEd448V6
                ha
                st
                salt
                sk
                hs
                us
                (payloadForUserId pkp uid)
        SPUat st uat ->
            signDataWithEd448V6 ha st salt sk hs us (payloadForUat pkp uat)
        SPDirectKey ->
            signDataWithEd448V6
                ha
                DirectKeySignature
                salt
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPKeyRevocation ->
            signDataWithEd448V6
                ha
                KeyRevocationSig
                salt
                sk
                hs
                us
                (payloadForDirectKey pkp)
        SPSubkeyRevocation subKp ->
            signDataWithEd448V6
                ha
                SubkeyRevocationSig
                salt
                sk
                hs
                us
                (payloadForSubkeyRevocation pkp (keyPktPKPayload subKp))
        SPCertRevocation uid ->
            signDataWithEd448V6
                ha
                CertRevocationSig
                salt
                sk
                hs
                us
                (payloadForCertRevocation pkp uid)
        SPSignSubkeyBinding subKp ->
            signDataWithEd448V6
                ha
                SubkeyBindingSig
                salt
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPPrimaryKeyBinding subKp ->
            signDataWithEd448V6
                ha
                PrimaryKeyBindingSig
                salt
                sk
                hs
                us
                (payloadForSubkeyBinding pkp (keyPktPKPayload subKp))
        SPRaw st raw -> signDataWithEd448V6 ha st salt sk hs us (BL.fromStrict raw)
  where
    pkp = keyPktPKPayload kp

signingErrorFromTarget :: SigningTarget -> SigningError
signingErrorFromTarget SignWithPrimary = SigningNoSignersAvailable
signingErrorFromTarget (SignWithKey kid) = SigningInvalidTarget (PKA.unEOKI kid)
signingErrorFromTarget SignWithBest = SigningNoSignersAvailable
signingErrorFromTarget (SignWithBestFilter _) = SigningNoSignersAvailable

resolveTarget
    :: SigningTarget -> [AvailableSigner] -> Maybe AvailableSigner
resolveTarget SignWithPrimary signers = listToMaybe (filter asIsPrimary signers)
resolveTarget (SignWithKey kid) signers = find (\s -> asKeyId s == kid) signers
resolveTarget SignWithBest signers = listToMaybe signers
resolveTarget (SignWithBestFilter p) signers = find p signers

-- | Sign a user ID with the primary key.
signUserId
    :: (MonadRandom m)
    => SigType
    -> UserId
    -> SigningT 'SecretTK m (Either SigningError SignaturePayload)
signUserId st uid = do
    result <- signWith SignWithPrimary (SPUserId st uid)
    pure result

-- | Sign a user attribute with the primary key.
signUat
    :: (MonadRandom m)
    => SigType
    -> UserAttribute
    -> SigningT 'SecretTK m (Either SigningError SignaturePayload)
signUat st uat = do
    result <- signWith SignWithPrimary (SPUat st uat)
    pure result

-- | Get the current signing timestamp.
getCurrentTimestamp
    :: Monad m => SigningT tk m ThirtyTwoBitTimeStamp
getCurrentTimestamp = SigningT $ lift get

-- | Set the current signing timestamp.
setCurrentTimestamp
    :: Monad m => ThirtyTwoBitTimeStamp -> SigningT tk m ()
setCurrentTimestamp ts = SigningT $ lift $ put ts

-- | Execute an action with a specific timestamp.
withTimestamp
    :: Monad m
    => ThirtyTwoBitTimeStamp -> SigningT tk m a -> SigningT tk m a
withTimestamp ts action = do
    old <- getCurrentTimestamp
    setCurrentTimestamp ts
    result <- action
    setCurrentTimestamp old
    pure result

-- | Filter to only signing-capable keys.
filterSigningCapable :: [AvailableSigner] -> [AvailableSigner]
filterSigningCapable = filter (\s -> isSigningCapable (asUsage s))

-- | Filter by key ID.
filterByKeyId
    :: PKA.EightOctetKeyId -> [AvailableSigner] -> [AvailableSigner]
filterByKeyId kid = filter (\s -> asKeyId s == kid)

-- | Filter by fingerprint.
filterByFingerprint
    :: PKA.Fingerprint -> [AvailableSigner] -> [AvailableSigner]
filterByFingerprint fp = filter (\s -> asFingerprint s == fp)