hOpenPGP-3.0.0: Data/Conduit/OpenPGP/Keyring/Instances.hs
-- Instances.hs: OpenPGP (RFC9580) additional types for transferable keys
-- Copyright © 2012-2026 Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Data.Conduit.OpenPGP.Keyring.Instances
(
) where
import Codec.Encryption.OpenPGP.Fingerprint (eightOctetKeyID, fingerprint)
import Codec.Encryption.OpenPGP.Internal (issuer)
import Codec.Encryption.OpenPGP.SignatureQualities (sigCT)
import Codec.Encryption.OpenPGP.Types
import Control.Arrow (second)
import Control.Lens ((^.), (^..), _1, folded)
import Data.Data.Lens (biplate)
import Data.Either (rights)
import Data.Function (on)
import qualified Data.HashMap.Lazy as HashMap
import Data.IxSet.Typed (Indexable(..), ixFun, ixList)
import Data.List (nub, sort)
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as Map
import Data.Semigroup (Semigroup, (<>))
import Data.Text (Text)
instance Indexable KeyringIxs TKUnknown where
indices = ixList (ixFun getEOKIs) (ixFun getFingerprints) (ixFun getUIDs)
getEOKIs :: TKUnknown -> [EightOctetKeyId]
getEOKIs tk = rights (map eightOctetKeyID (tk ^.. biplate :: [SomePKPayload]))
getFingerprints :: TKUnknown -> [Fingerprint]
getFingerprints tk = map fingerprint (tk ^.. biplate :: [SomePKPayload])
getUIDs :: TKUnknown -> [Text]
getUIDs tk = (tk ^. tkuUIDs) ^.. folded . _1
instance Semigroup TKUnknown where
(<>) a b =
TKUnknown
(_tkuKey a)
(nub . sort $ _tkuRevs a ++ _tkuRevs b)
((kvmerge `on` _tkuUIDs) a b)
((kvmerge `on` _tkuUAts) a b)
((ukvmerge `on` _tkuSubs) a b)
where
kvmerge x y =
Map.toList (Map.unionWith nsa (Map.fromList x) (Map.fromList y))
ukvmerge x y =
HashMap.toList
(HashMap.unionWith nsa (HashMap.fromList x) (HashMap.fromList y))
nsa x y = nub . sort $ x ++ y
instance Semigroup TKWithWireRep where
(<>) a b =
let mergedTK = _tkValue a <> _tkValue b
mergedPackets =
selectPacketRefsByValue
(flattenTKPackets mergedTK)
(dedupePacketRefsById (_tkPackets a ++ _tkPackets b))
in TKWithWireRep
(mergeWireRepRefs (_tkWireRepRefs a) (_tkWireRepRefs b))
(mergedWireRepRange mergedPackets)
mergedPackets
mergedTK
flattenTKPackets :: TKUnknown -> [Pkt]
flattenTKPackets tk =
[someKeyPktToPkt (mkPrimaryKeyPkt pkp mska)] ++
map SignaturePkt (_tkuRevs tk) ++
concatMap flattenUID (_tkuUIDs tk) ++
concatMap flattenUAT (_tkuUAts tk) ++
concatMap flattenSub (_tkuSubs tk)
where
(pkp, mska) = _tkuKey tk
flattenUID (uid, sigs) = UserIdPkt uid : map SignaturePkt sigs
flattenUAT (uat, sigs) = UserAttributePkt uat : map SignaturePkt sigs
flattenSub (pkt, sigs) = pkt : map SignaturePkt sigs
mergeWireRepRefs :: WireRepRefs -> WireRepRefs -> WireRepRefs
mergeWireRepRefs left right =
case dedupe (NE.toList left ++ NE.toList right) of
[] -> left
(x:xs) -> x NE.:| xs
where
dedupe [] = []
dedupe (x:xs) = x : dedupe (filter (/= x) xs)
mergedWireRepRange :: [PktWithWireRep] -> Maybe ByteRange
mergedWireRepRange [] = Nothing
mergedWireRepRange (pkt:rest)
| all ((== _pktWireRepRef pkt) . _pktWireRepRef) rest =
spanByteRanges (map _pktRange (pkt : rest))
| otherwise = Nothing
dedupePacketRefsById :: [PktWithWireRep] -> [PktWithWireRep]
dedupePacketRefsById = go []
where
go _ [] = []
go seen (pkt:rest) =
let packetRefId = packetRefIdOf pkt
in if packetRefId `elem` seen
then go seen rest
else pkt : go (packetRefId : seen) rest
selectPacketRefsByValue :: [Pkt] -> [PktWithWireRep] -> [PktWithWireRep]
selectPacketRefsByValue expected available = go expected available []
where
go [] _ acc = reverse acc
go (pkt:pktRest) refs acc =
case extractFirstByValue pkt refs of
Nothing ->
error
("TKWithWireRep Semigroup merge missing packet reference for tag " ++
show (pktTag pkt))
Just (matched, remaining) -> go pktRest remaining (matched : acc)
extractFirstByValue :: Pkt -> [PktWithWireRep] -> Maybe (PktWithWireRep, [PktWithWireRep])
extractFirstByValue _ [] = Nothing
extractFirstByValue expected (pkt:rest)
| _pktValue pkt == expected = Just (pkt, rest)
| otherwise = second (pkt :) <$> extractFirstByValue expected rest
-- | Extract all SomePKPayloads from a TK (primary + subkeys) without biplate
tkPKPayloads :: TK k -> [SomePKPayload]
tkPKPayloads tk =
keyPktPKPayload (_tkPrimaryKey tk) :
map (keyPktPKPayload . fst) (_tkSubs tk)
-- | Index public TKs by key ID, fingerprint, and UID
instance Indexable KeyringIxs (TK 'PublicTK) where
indices = ixList (ixFun getEOKIsPublic) (ixFun getFingerprintsPublic) (ixFun getUIDsPublic)
getEOKIsPublic :: TK 'PublicTK -> [EightOctetKeyId]
getEOKIsPublic tk = rights (map eightOctetKeyID (tkPKPayloads tk))
getFingerprintsPublic :: TK 'PublicTK -> [Fingerprint]
getFingerprintsPublic tk = map fingerprint (tkPKPayloads tk)
getUIDsPublic :: TK 'PublicTK -> [Text]
getUIDsPublic tk = (tk ^. tkUIDs) ^.. folded . _1
-- | Index secret TKs by key ID, fingerprint, and UID
instance Indexable KeyringIxs (TK 'SecretTK) where
indices = ixList (ixFun getEOKIsSecret) (ixFun getFingerprintsSecret) (ixFun getUIDsSecret)
getEOKIsSecret :: TK 'SecretTK -> [EightOctetKeyId]
getEOKIsSecret tk = rights (map eightOctetKeyID (tkPKPayloads tk))
getFingerprintsSecret :: TK 'SecretTK -> [Fingerprint]
getFingerprintsSecret tk = map fingerprint (tkPKPayloads tk)
getUIDsSecret :: TK 'SecretTK -> [Text]
getUIDsSecret tk = (tk ^. tkUIDs) ^.. folded . _1
instance Semigroup (TK k) where
a <> b =
TK
(_tkPrimaryKey a)
(nub . sort $ _tkRevs a ++ _tkRevs b)
((kvmerge `on` _tkUIDs) a b)
((kvmerge `on` _tkUAts) a b)
((ukvmerge `on` _tkSubs) a b)
where
kvmerge x y =
Map.toList (Map.unionWith nsa (Map.fromList x) (Map.fromList y))
ukvmerge x y =
HashMap.toList
(HashMap.unionWith nsa (HashMap.fromList x) (HashMap.fromList y))
nsa x y = nub . sort $ x ++ y