hOpenPGP-3.0.0: Codec/Encryption/OpenPGP/Serialize.hs
-- Serialize.hs: OpenPGP (RFC9580) serialization (using binary)
-- Copyright © 2012-2026 Clint Adams
-- This software is released under the terms of the Expat license.
-- (See the LICENSE file).
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}
module Codec.Encryption.OpenPGP.Serialize
(
-- * Serialization functions
putPkt
, putPktEither
, putSKAddendum
, getSecretKey
, putSKeyForPKPayload
-- * Utilities
, dearmorIfAsciiArmored
, WireRepInput(..)
, wireRepRefFromInput
, PktParseError(..)
, parsePkts
, parsePktsEither
, parsePktsWithWireRep
, conduitParsePktsWithWireRep
) where
import Control.Applicative (many, some)
import Control.Arrow ((***))
import Control.Lens ((^.), _1)
import Control.Monad (guard, replicateM, replicateM_, when)
import Crypto.Number.Basic (numBits)
import Crypto.Number.ModArithmetic (inverse)
import Crypto.Number.Serialize (i2osp, os2ip)
import qualified Crypto.PubKey.DSA as D
import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
import qualified Crypto.PubKey.ECC.Types as ECCT
import qualified Crypto.PubKey.RSA as R
import Data.Bifunctor (bimap)
import Data.Binary (Binary, get, put)
import Data.Binary.Get
( ByteOffset
, Get
, bytesRead
, getByteString
, getLazyByteString
, getRemainingLazyByteString
, getWord16be
, getWord16le
, getWord32be
, getWord8
, lookAhead
, runGetOrFail
)
import Data.Binary.Put
( Put
, putByteString
, putLazyByteString
, putWord16be
, putWord16le
, putWord32be
, putWord8
, runPut
)
import Data.Bits ((.&.), (.|.), shiftL, shiftR, testBit)
import qualified Data.ByteString as B
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Char8 as BLC8
import qualified Data.Foldable as F
import Data.Int (Int64)
import Data.List (mapAccumL)
import qualified Data.List.NonEmpty as NE
import Data.Maybe (fromMaybe)
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
import Data.Text.Encoding.Error (lenientDecode)
import Data.Word (Word16, Word32, Word8)
import Network.URI (nullURI, parseURI, uriToString)
import Codec.Encryption.OpenPGP.Internal
( curve2Curve
, curveFromCurve
, curveToCurveoidBS
, curveoidBSToCurve
, curveoidBSToEdSigningCurve
, edSigningCurveToCurveoidBS
, leftPadTo
, pubkeyToMPIs
)
import Codec.Encryption.OpenPGP.Policy (signatureV6SaltSizeForHashAlgorithm)
import Codec.Encryption.OpenPGP.Types
import qualified Codec.Encryption.OpenPGP.ASCIIArmor as AA
import Codec.Encryption.OpenPGP.ASCIIArmor.Types (Armor(..))
import Data.Conduit (ConduitT, await, yield)
import qualified Codec.Encryption.OpenPGP.Types.Internal.Base as BTypes
import qualified Codec.Encryption.OpenPGP.Types.Internal.PKITypes as P
instance Binary SigSubPacket where
get = getSigSubPacket
put = putSigSubPacket
-- instance Binary (Set NotationFlag) where
-- put = putNotationFlagSet
instance Binary CompressionAlgorithm where
get = toFVal <$> getWord8
put = putWord8 . fromFVal
instance Binary PubKeyAlgorithm where
get = toFVal <$> getWord8
put = putWord8 . fromFVal
instance Binary HashAlgorithm where
get = toFVal <$> getWord8
put = putWord8 . fromFVal
instance Binary SymmetricAlgorithm where
get = toFVal <$> getWord8
put = putWord8 . fromFVal
instance Binary MPI where
get = getMPI
put = putMPI
instance Binary SigType where
get = toFVal <$> getWord8
put = putWord8 . fromFVal
instance Binary UserAttrSubPacket where
get = getUserAttrSubPacket
put = putUserAttrSubPacket
instance Binary S2K where
get = getS2K
put = putS2K
instance Binary (PKESK 'PKESKV3) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary (PKESK 'PKESKV6) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary Signature where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary (SKESK 'SKESKV4) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary (SKESK 'SKESKV6) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary (OnePassSignature 'OPSV3) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary (OnePassSignature 'OPSV6) where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary SecretKey where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary PublicKey where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary SecretSubkey where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary CompressedData where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary SymEncData where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary Marker where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary LiteralData where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary Trust where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary UserId where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary PublicSubkey where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary UserAttribute where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary SymEncIntegrityProtectedData where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary ModificationDetectionCode where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary OtherPacket where
get = getPkt >>= either fail pure . fromPktEither
put = putPkt . toPkt
instance Binary Pkt where
get = getPkt
put = putPkt
instance Binary a => Binary (Block a) where
get = Block `fmap` many get
put = mapM_ put . unBlock
instance Binary SomePKPayload where
get = getPKPayload
put = putPKPayload
instance Binary SignaturePayload where
get = getSignaturePayload
put = putSignaturePayload
instance Binary TKUnknown where
get = fail "Binary TKUnknown decode is not implemented"
put = putTK
getSigSubPacket :: Get SigSubPacket
getSigSubPacket = do
l <- fmap fromIntegral getSubPacketLength
(crit, pt) <- getSigSubPacketType
getSigSubPacket' pt crit l
where
getSigSubPacket' :: Word8 -> Bool -> ByteOffset -> Get SigSubPacket
getSigSubPacket' pt crit l
| pt == 2 = do
et <- fmap ThirtyTwoBitTimeStamp getWord32be
return $ SigSubPacket crit (SigCreationTime et)
| pt == 3 = do
et <- fmap ThirtyTwoBitDuration getWord32be
return $ SigSubPacket crit (SigExpirationTime et)
| pt == 4 = do
e <- get
return $ SigSubPacket crit (ExportableCertification e)
| pt == 5 = do
tl <- getWord8
ta <- getWord8
return $ SigSubPacket crit (TrustSignature tl ta)
| pt == 6 = do
apdre <- getLazyByteString (l - 2)
nul <- getWord8
guard (nul == 0)
return $ SigSubPacket crit (RegularExpression (BL.copy apdre))
| pt == 7 = do
r <- get
return $ SigSubPacket crit (Revocable r)
| pt == 9 = do
et <- fmap ThirtyTwoBitDuration getWord32be
return $ SigSubPacket crit (KeyExpirationTime et)
| pt == 11 = do
sa <- replicateM (fromIntegral (l - 1)) get
return $ SigSubPacket crit (PreferredSymmetricAlgorithms sa)
| pt == 12 = do
rclass <- getWord8
guard (testBit rclass 7)
algid <- get
fp <- getLazyByteString (fromIntegral l - 3)
return $
SigSubPacket
crit
(RevocationKey
(bsToFFSet . BL.singleton $ rclass .&. 0x7f)
algid
(Fingerprint fp))
| pt == 16 = do
keyid <- getLazyByteString (l - 1)
return $ SigSubPacket crit (Issuer (EightOctetKeyId keyid))
| pt == 20 = do
flags <- getLazyByteString 4
nl <- getWord16be
vl <- getWord16be
nn <- getLazyByteString (fromIntegral nl)
nv <- getLazyByteString (fromIntegral vl)
return $
SigSubPacket
crit
(NotationData (bsToFFSet flags) (NotationName nn) (NotationValue nv))
| pt == 21 = do
ha <- replicateM (fromIntegral (l - 1)) get
return $ SigSubPacket crit (PreferredHashAlgorithms ha)
| pt == 22 = do
ca <- replicateM (fromIntegral (l - 1)) get
return $ SigSubPacket crit (PreferredCompressionAlgorithms ca)
| pt == 23 = do
ksps <- getLazyByteString (l - 1)
return $ SigSubPacket crit (KeyServerPreferences (bsToFFSet ksps))
| pt == 24 = do
pks <- getLazyByteString (l - 1)
return $ SigSubPacket crit (PreferredKeyServer pks)
| pt == 25 = do
primacy <- get
return $ SigSubPacket crit (PrimaryUserId primacy)
| pt == 26 = do
url <-
fmap
(URL . fromMaybe nullURI . parseURI . T.unpack .
decodeUtf8With lenientDecode)
(getByteString (fromIntegral (l - 1)))
return $ SigSubPacket crit (PolicyURL url)
| pt == 27 = do
kfs <- getLazyByteString (l - 1)
return $ SigSubPacket crit (KeyFlags (bsToFFSet kfs))
| pt == 28 = do
uid <- getByteString (fromIntegral (l - 1))
return $
SigSubPacket crit (SignersUserId (decodeUtf8With lenientDecode uid))
| pt == 29 = do
rcode <- getWord8
rreason <-
fmap
(decodeUtf8With lenientDecode)
(getByteString (fromIntegral (l - 2)))
return $ SigSubPacket crit (ReasonForRevocation (toFVal rcode) rreason)
| pt == 30 = do
fbs <- getLazyByteString (l - 1)
return $ SigSubPacket crit (Features (bsToFFSet fbs))
| pt == 31 = do
pka <- get
ha <- get
hash <- getLazyByteString (l - 3)
return $ SigSubPacket crit (SignatureTarget pka ha hash)
| pt == 32 = do
spbs <- getLazyByteString (l - 1)
case runGetOrFail get spbs of
Left (_, _, e) -> fail ("embedded signature subpacket " ++ e)
Right (_, _, sp) -> return $ SigSubPacket crit (EmbeddedSignature sp)
| pt == 33 = do
when (l /= 22 && l /= 34) $
fail ("invalid issuer fingerprint subpacket length: " ++ show l)
kv <- getWord8
let fpLen = l - 2
when (fpLen /= 20 && fpLen /= 32) $
fail ("invalid issuer fingerprint length: " ++ show fpLen)
case BTypes.packetVersionToIssuerFingerprintVersion kv of
Nothing ->
fail ("invalid issuer fingerprint version marker: " ++ show kv)
Just ifVersion -> do
fp <-
case kv of
4 -> getLazyByteString (fromIntegral fpLen)
6 -> getLazyByteString (fromIntegral fpLen)
_ -> fail ("invalid issuer fingerprint version marker: " ++ show kv)
return $
SigSubPacket crit (IssuerFingerprint ifVersion (Fingerprint fp))
| pt > 99 && pt < 111 = do
payload <- getLazyByteString (l - 1)
return $ SigSubPacket crit (UserDefinedSigSub pt payload)
| otherwise = do
payload <- getLazyByteString (l - 1)
return $ SigSubPacket crit (OtherSigSub pt payload)
putSigSubPacket :: SigSubPacket -> Put
putSigSubPacket (SigSubPacket crit (SigCreationTime et)) = do
putSubPacketLength 5
putSigSubPacketType crit 2
putWord32be . unThirtyTwoBitTimeStamp $ et
putSigSubPacket (SigSubPacket crit (SigExpirationTime et)) = do
putSubPacketLength 5
putSigSubPacketType crit 3
putWord32be . unThirtyTwoBitDuration $ et
putSigSubPacket (SigSubPacket crit (ExportableCertification e)) = do
putSubPacketLength 2
putSigSubPacketType crit 4
put e
putSigSubPacket (SigSubPacket crit (TrustSignature tl ta)) = do
putSubPacketLength 3
putSigSubPacketType crit 5
put tl
put ta
putSigSubPacket (SigSubPacket crit (RegularExpression apdre)) = do
putSubPacketLength . fromIntegral $ (2 + BL.length apdre)
putSigSubPacketType crit 6
putLazyByteString apdre
putWord8 0
putSigSubPacket (SigSubPacket crit (Revocable r)) = do
putSubPacketLength 2
putSigSubPacketType crit 7
put r
putSigSubPacket (SigSubPacket crit (KeyExpirationTime et)) = do
putSubPacketLength 5
putSigSubPacketType crit 9
putWord32be . unThirtyTwoBitDuration $ et
putSigSubPacket (SigSubPacket crit (PreferredSymmetricAlgorithms ess)) = do
putSubPacketLength . fromIntegral $ (1 + length ess)
putSigSubPacketType crit 11
mapM_ put ess
putSigSubPacket (SigSubPacket crit (RevocationKey rclass algid fp)) = do
let fpLen = BL.length (unFingerprint fp)
putSubPacketLength (fromIntegral (3 + fpLen)) -- type(1) + rclass(1) + algid(1) + fingerprint
putSigSubPacketType crit 12
putLazyByteString . ffSetToFixedLengthBS (1 :: Int) $
Set.insert (RClOther 0) rclass
put algid
putLazyByteString (unFingerprint fp)
putSigSubPacket (SigSubPacket crit (Issuer keyid)) = do
putSubPacketLength 9
putSigSubPacketType crit 16
putLazyByteString (unEOKI keyid) -- 8 octets
putSigSubPacket (SigSubPacket crit (NotationData nfs (NotationName nn) (NotationValue nv))) = do
putSubPacketLength . fromIntegral $ (9 + BL.length nn + BL.length nv)
putSigSubPacketType crit 20
putLazyByteString . ffSetToFixedLengthBS (4 :: Int) $ nfs
putWord16be . fromIntegral . BL.length $ nn
putWord16be . fromIntegral . BL.length $ nv
putLazyByteString nn
putLazyByteString nv
putSigSubPacket (SigSubPacket crit (PreferredHashAlgorithms ehs)) = do
putSubPacketLength . fromIntegral $ (1 + length ehs)
putSigSubPacketType crit 21
mapM_ put ehs
putSigSubPacket (SigSubPacket crit (PreferredCompressionAlgorithms ecs)) = do
putSubPacketLength . fromIntegral $ (1 + length ecs)
putSigSubPacketType crit 22
mapM_ put ecs
putSigSubPacket (SigSubPacket crit (KeyServerPreferences ksps)) = do
let kbs = ffSetToBS ksps
putSubPacketLength . fromIntegral $ (1 + BL.length kbs)
putSigSubPacketType crit 23
putLazyByteString kbs
putSigSubPacket (SigSubPacket crit (PreferredKeyServer ks)) = do
putSubPacketLength . fromIntegral $ (1 + BL.length ks)
putSigSubPacketType crit 24
putLazyByteString ks
putSigSubPacket (SigSubPacket crit (PrimaryUserId primacy)) = do
putSubPacketLength 2
putSigSubPacketType crit 25
put primacy
putSigSubPacket (SigSubPacket crit (PolicyURL (URL uri))) = do
let bs = encodeUtf8 (T.pack (uriToString id uri ""))
putSubPacketLength . fromIntegral $ (1 + B.length bs)
putSigSubPacketType crit 26
putByteString bs
putSigSubPacket (SigSubPacket crit (KeyFlags kfs)) = do
let kbs = ffSetToBS kfs
putSubPacketLength . fromIntegral $ (1 + BL.length kbs)
putSigSubPacketType crit 27
putLazyByteString kbs
putSigSubPacket (SigSubPacket crit (SignersUserId userid)) = do
let bs = encodeUtf8 userid
putSubPacketLength . fromIntegral $ (1 + B.length bs)
putSigSubPacketType crit 28
putByteString bs
putSigSubPacket (SigSubPacket crit (ReasonForRevocation rcode rreason)) = do
let reasonbs = encodeUtf8 rreason
putSubPacketLength . fromIntegral $ (2 + B.length reasonbs)
putSigSubPacketType crit 29
putWord8 . fromFVal $ rcode
putByteString reasonbs
putSigSubPacket (SigSubPacket crit (Features fs)) = do
let fbs = ffSetToBS fs
putSubPacketLength . fromIntegral $ (1 + BL.length fbs)
putSigSubPacketType crit 30
putLazyByteString fbs
putSigSubPacket (SigSubPacket crit (SignatureTarget pka ha hash)) = do
putSubPacketLength . fromIntegral $ (3 + BL.length hash)
putSigSubPacketType crit 31
put pka
put ha
putLazyByteString hash
putSigSubPacket (SigSubPacket crit (EmbeddedSignature sp)) = do
let spb = runPut (put sp)
putSubPacketLength . fromIntegral $ (1 + BL.length spb)
putSigSubPacketType crit 32
putLazyByteString spb
putSigSubPacket (SigSubPacket crit (IssuerFingerprint kv fp)) = do
let kv' = BTypes.issuerFingerprintVersionToPacketVersion kv
let fpb = unFingerprint fp
when (BL.length fpb /= 20 && BL.length fpb /= 32) $
error ("invalid issuer fingerprint length: " ++ show (BL.length fpb))
putSubPacketLength . fromIntegral $ (2 + BL.length fpb)
putSigSubPacketType crit 33
putWord8 kv'
putLazyByteString fpb
putSigSubPacket (SigSubPacket crit (UserDefinedSigSub ptype payload)) =
putSigSubPacket (SigSubPacket crit (OtherSigSub ptype payload))
putSigSubPacket (SigSubPacket crit (OtherSigSub ptype payload)) = do
putSubPacketLength . fromIntegral $ (1 + BL.length payload)
putSigSubPacketType crit ptype
putLazyByteString payload
getSubPacketLength :: Get Word32
getSubPacketLength = getSubPacketLength' =<< getWord8
where
getSubPacketLength' :: Integral a => Word8 -> Get a
getSubPacketLength' f
| f < 192 = return . fromIntegral $ f
| f < 224 = do
secondOctet <- getWord8
return . fromIntegral $ shiftL (fromIntegral (f - 192) :: Int) 8 +
(fromIntegral secondOctet :: Int) +
192
| f == 255 = do
len <- getWord32be
return . fromIntegral $ len
| otherwise = fail "Partial body length invalid."
putSubPacketLength :: Word32 -> Put
putSubPacketLength l
| l < 192 = putWord8 (fromIntegral l)
| l < 8384 =
putWord8 (fromIntegral ((fromIntegral (l - 192) `shiftR` 8) + 192 :: Int)) >>
putWord8 (fromIntegral (l - 192) .&. 0xff)
| l <= 0xffffffff = putWord8 255 >> putWord32be (fromIntegral l)
| otherwise = error ("too big (" ++ show l ++ ")")
getSigSubPacketType :: Get (Bool, Word8)
getSigSubPacketType = do
x <- getWord8
return
(if x .&. 128 == 128
then (True, x .&. 127)
else (False, x))
putSigSubPacketType :: Bool -> Word8 -> Put
putSigSubPacketType False sst = putWord8 sst
putSigSubPacketType True sst = putWord8 (sst .|. 0x80)
bsToFFSet :: FutureFlag a => ByteString -> Set a
bsToFFSet bs =
Set.fromAscList . concat . snd $
mapAccumL
(\acc y -> (acc + 8, concatMap (shifty acc y) [0 .. 7]))
0
(BL.unpack bs)
where
shifty acc y x = [toFFlag (acc + x) | y .&. shiftR 128 x == shiftR 128 x]
ffSetToFixedLengthBS :: (Integral a, FutureFlag b) => a -> Set b -> ByteString
ffSetToFixedLengthBS len ffs =
BL.take
(fromIntegral len)
(BL.append (ffSetToBS ffs) (BL.pack (replicate 5 0)))
ffSetToBS :: FutureFlag a => Set a -> ByteString
ffSetToBS = BL.pack . ffSetToBS'
where
ffSetToBS' :: FutureFlag a => Set a -> [Word8]
ffSetToBS' ks
-- Emit a single zero octet for an empty flag set so encoded flag
-- subpackets always carry an explicit flags byte.
| Set.null ks = [0]
| otherwise =
map
((foldl (.|.) 0 . map (shiftR 128 . flip mod 8 . fromFFlag) .
Set.toAscList) .
(\x -> Set.filter (\y -> fromFFlag y `div` 8 == x) ks))
[0 .. fromFFlag (Set.findMax ks) `div` 8]
fromS2K :: S2K -> ByteString
fromS2K (Simple hashalgo) = BL.pack [0, fromIntegral . fromFVal $ hashalgo]
fromS2K (Salted hashalgo salt) =
BL.pack [1, fromIntegral . fromFVal $ hashalgo] `BL.append`
(BL.fromStrict . unSalt8) salt
fromS2K (IteratedSalted hashalgo salt count) =
BL.pack [3, fromIntegral . fromFVal $ hashalgo] `BL.append`
(BL.fromStrict . unSalt8) salt `BL.snoc`
encodeIterationCount count
fromS2K (Argon2 salt t p encodedM) =
BL.pack [4] `BL.append` (BL.fromStrict . unSalt16) salt `BL.append`
BL.pack [t, p, encodedM]
fromS2K (OtherS2K _ bs) = bs
getPacketLength :: Get Integer
getPacketLength = do
firstOctet <- getWord8
lenOrPartial <- lengthOctetToLength firstOctet
case lenOrPartial of
Left _ ->
fail "Partial body length is invalid in this context"
Right len -> return len
where
lengthOctetToLength :: Word8 -> Get (Either Integer Integer)
lengthOctetToLength f
| f < 192 = return . Right . fromIntegral $ f
| f < 224 = do
secondOctet <- getWord8
return . Right . fromIntegral $
shiftL (fromIntegral (f - 192) :: Int) 8 +
(fromIntegral secondOctet :: Int) +
192
| f < 255 =
return . Left . fromIntegral $ (1 :: Integer) `shiftL` fromIntegral (f .&. 0x1f)
| otherwise = do
len <- getWord32be
return . Right . fromIntegral $ len
putPacketLength :: Integer -> Put
putPacketLength l
| l < 192 = putWord8 (fromIntegral l)
| l < 8384 =
putWord8 (fromIntegral ((fromIntegral (l - 192) `shiftR` 8) + 192 :: Int)) >>
putWord8 (fromIntegral (l - 192) .&. 0xff)
| l < 0x100000000 = putWord8 255 >> putWord32be (fromIntegral l)
| otherwise = error "packet length exceeds 32-bit definite length encoding"
putPartialLength :: Word8 -> Put
putPartialLength n = putWord8 (224 + n)
getPacketLengthFromOctet :: Word8 -> Get (Either Int64 Int64)
getPacketLengthFromOctet f
| f < 192 = return . Right . fromIntegral $ f
| f < 224 = do
secondOctet <- getWord8
return . Right . fromIntegral $
shiftL (fromIntegral (f - 192) :: Int) 8 +
(fromIntegral secondOctet :: Int) +
192
| f < 255 =
return . Left . fromIntegral $ (1 :: Integer) `shiftL` fromIntegral (f .&. 0x1f)
| otherwise = do
len <- getWord32be
return . Right . fromIntegral $ len
getS2K :: Get S2K
getS2K = getS2K' =<< getWord8
where
getS2K' :: Word8 -> Get S2K
getS2K' t
| t == 0 = do
ha <- getWord8
return $ Simple (toFVal ha)
| t == 1 = do
ha <- getWord8
salt <- getByteString 8
return $ Salted (toFVal ha) (Salt8 salt)
| t == 3 = do
ha <- getWord8
salt <- getByteString 8
count <- getWord8
return $
IteratedSalted (toFVal ha) (Salt8 salt) (decodeIterationCount count)
| t == 4 = do
salt <- getByteString 16
passes <- getWord8
parallelism <- getWord8
encodedM <- getWord8
return $ Argon2 (Salt16 salt) passes parallelism encodedM
| otherwise = do
bs <- getRemainingLazyByteString
return $ OtherS2K t bs
putS2K :: S2K -> Put
putS2K (Simple hashalgo) = error ("confused by simple" ++ show hashalgo)
putS2K (Salted hashalgo salt) =
error ("confused by salted" ++ show hashalgo ++ " by " ++ show salt)
putS2K (IteratedSalted ha salt count) = do
putWord8 3
put ha
putByteString (unSalt8 salt)
putWord8 $ encodeIterationCount count
putS2K (Argon2 salt t p encodedM) = do
putWord8 4
putByteString (unSalt16 salt)
putWord8 t
putWord8 p
putWord8 encodedM
putS2K (OtherS2K t bs) = putWord8 t >> putLazyByteString bs
v6SaltSizeForHashAlgorithm :: HashAlgorithm -> Maybe Word8
v6SaltSizeForHashAlgorithm = signatureV6SaltSizeForHashAlgorithm
getPacketTypeAndPayload :: Get (Word8, ByteString)
getPacketTypeAndPayload = do
tag <- getWord8
guard (testBit tag 7)
case tag .&. 0x40 of
0x00 -> do
let t = shiftR (tag .&. 0x3c) 2
case tag .&. 0x03 of
0 -> do
len <- getWord8
bs <- getLazyByteString (fromIntegral len)
return (t, bs)
1 -> do
len <- getWord16be
bs <- getLazyByteString (fromIntegral len)
return (t, bs)
2 -> do
len <- getWord32be
bs <- getLazyByteString (fromIntegral len)
return (t, bs)
3 -> do
bs <- getRemainingLazyByteString
return (t, bs)
_ -> error "This should never happen (getPacketTypeAndPayload/0x00)."
0x40 -> do
firstLenOctet <- getWord8
bs <- getPacketPayloadFromLengthOctet firstLenOctet
return (tag .&. 0x3f, bs)
_ -> error "This should never happen (getPacketTypeAndPayload/???)."
where
getPacketPayloadFromLengthOctet :: Word8 -> Get ByteString
getPacketPayloadFromLengthOctet lenOctet = do
lenOrPartial <- getPacketLengthFromOctet lenOctet
case lenOrPartial of
Right len -> getLazyByteString len
Left partialLen -> do
chunk <- getLazyByteString partialLen
rest <- getRemainingPartialPayload
return (chunk <> rest)
getRemainingPartialPayload :: Get ByteString
getRemainingPartialPayload = do
lenOctet <- getWord8
lenOrPartial <- getPacketLengthFromOctet lenOctet
case lenOrPartial of
Right len -> getLazyByteString len
Left partialLen -> do
chunk <- getLazyByteString partialLen
(chunk <>) <$> getRemainingPartialPayload
getPkt :: Get Pkt
getPkt = do
(t, pl) <- getPacketTypeAndPayload
case runGetOrFail (getPkt' t (BL.length pl)) pl of
Left (_, _, e) -> return $! BrokenPacketPkt e t pl
Right (_, _, p) -> return p
where
parseLegacyPKESK :: PacketVersion -> BL.ByteString -> Either String Pkt
parseLegacyPKESK pv body = do
(_, _, (eokeyid, pkaRaw, mpib)) <-
bimap (\(_, _, e) -> e) id $
runGetOrFail
(do eokeyid <- getLazyByteString 8
pka <- getWord8
mpib <- getRemainingLazyByteString
pure (eokeyid, pka, mpib))
body
let pka = toFVal pkaRaw
sk <- parseLegacyPKESKMPIs pka mpib
pure $
PKESKPkt
(PKESKPayloadV3Packet (PKESKPayloadV3 pv (EightOctetKeyId eokeyid) pka sk))
parseLegacyPKESKMPIs :: PubKeyAlgorithm -> BL.ByteString -> Either String (NE.NonEmpty MPI)
parseLegacyPKESKMPIs pka mpib = do
case parseLegacyPKESKMPIsStrict pka mpib of
Right sk -> pure sk
Left strictErr
| pka == X25519 ->
case parseLegacyPKESKX25519V3Octets mpib of
Right sk -> Right sk
Left octetErr ->
Left
(strictErr ++
"; also failed to parse RFC9580 X25519 v3 octet layout: " ++
octetErr)
| pka == ECDH ->
case parseLegacyPKESKECDHOctets mpib of
Right sk -> Right sk
Left octetErr ->
Left
(strictErr ++
"; also failed to parse RFC6637 ECDH v3 octet layout: " ++
octetErr)
| otherwise -> Left strictErr
parseLegacyPKESKMPIsStrict ::
PubKeyAlgorithm -> BL.ByteString -> Either String (NE.NonEmpty MPI)
parseLegacyPKESKMPIsStrict pka mpib = do
(rest, _, sk) <-
bimap (\(_, _, e) -> e) id $
runGetOrFail (parserForLegacyPKESKMPIs pka) mpib
if BL.null rest
then pure (NE.fromList sk)
else
Left
("unexpected trailing PKESK MPI data for algorithm " ++ show pka)
parseLegacyPKESKX25519V3Octets :: BL.ByteString -> Either String (NE.NonEmpty MPI)
parseLegacyPKESKX25519V3Octets mpib = do
if BL.length mpib < 33
then Left "X25519 v3 PKESK octet layout is too short"
else Right ()
let ephemeral = BL.toStrict (BL.take 32 mpib)
eskLen = fromIntegral (BL.index mpib 32) :: Int
eskWithAlgo = BL.toStrict (BL.drop 33 mpib)
if eskLen /= B.length eskWithAlgo
then Left "X25519 v3 PKESK octet layout has inconsistent ESK length"
else Right ()
if B.null eskWithAlgo
then Left "X25519 v3 PKESK octet layout must include a symmetric algorithm octet"
else Right ()
let symAlgo = B.head eskWithAlgo
if symAlgo `elem` [fromIntegral (fromFVal AES128), fromIntegral (fromFVal AES192), fromIntegral (fromFVal AES256)]
then pure (NE.fromList [MPI (os2ip ephemeral), MPI (os2ip eskWithAlgo)])
else
Left
("X25519 v3 PKESK octet layout has unsupported symmetric algorithm octet " ++
show symAlgo)
-- | Parse an RFC 6637 §8 ECDH PKESKv3 body as MPI(ephemeral) || 1-octet-count || C.
-- This is the interoperable wire format produced by GnuPG and other RFC-compliant
-- implementations. hOpenPGP previously wrote both fields as MPIs; this fallback
-- allows reading RFC-compliant packets when the strict two-MPI path fails.
parseLegacyPKESKECDHOctets :: BL.ByteString -> Either String (NE.NonEmpty MPI)
parseLegacyPKESKECDHOctets mpib = do
(rest, _, ephMPI) <-
bimap (\(_, _, e) -> e) id $ runGetOrFail getMPI mpib
let restBS = BL.toStrict rest
when (B.null restBS) $
Left "ECDH v3 PKESK RFC6637 octet layout: missing wrapped-key length octet after ephemeral MPI"
let wrappedLen = fromIntegral (B.head restBS) :: Int
wrapped = B.tail restBS
when (wrappedLen /= B.length wrapped) $
Left
("ECDH v3 PKESK RFC6637 octet layout: wrapped key length field " ++
show wrappedLen ++ " does not match body length " ++ show (B.length wrapped))
when (wrappedLen < 24 || wrappedLen `mod` 8 /= 0) $
Left
("ECDH v3 PKESK RFC6637 octet layout: wrapped key length " ++
show wrappedLen ++ " is not a valid RFC 3394 wrapped key size")
pure (ephMPI NE.:| [MPI (os2ip wrapped)])
parserForLegacyPKESKMPIs :: PubKeyAlgorithm -> Get [MPI]
parserForLegacyPKESKMPIs pka =
case expectedLegacyPKESKMPIArity pka of
Just mpiCount -> replicateM mpiCount getMPI
Nothing -> some getMPI
expectedLegacyPKESKMPIArity :: PubKeyAlgorithm -> Maybe Int
expectedLegacyPKESKMPIArity pka
| pka `elem` [RSA, DeprecatedRSAEncryptOnly] = Just 1
| pka `elem` [ElgamalEncryptOnly, ForbiddenElgamal, ECDH, X25519] = Just 2
| otherwise = Nothing
validateV4SKESKEncryptedSessionKeyS2K :: S2K -> Maybe BL.ByteString -> Get ()
validateV4SKESKEncryptedSessionKeyS2K _ Nothing = pure ()
validateV4SKESKEncryptedSessionKeyS2K Simple {} (Just _) =
fail
"v4 SKESK packets with encrypted session keys must not use Simple S2K"
validateV4SKESKEncryptedSessionKeyS2K _ (Just _) = pure ()
parseV6PKESK :: BL.ByteString -> Either String Pkt
parseV6PKESK body = do
(_, _, (recipientKeyIdentifier, pka, esk)) <-
bimap (\(_, _, e) -> e) id $
runGetOrFail
(do keyIdentifierLen <- getWord8
recipientKeyIdentifier <- getLazyByteString (fromIntegral keyIdentifierLen)
pka <- getWord8
esk <- getRemainingLazyByteString
pure (recipientKeyIdentifier, pka, esk))
body
validateV6PKESKRecipientIdentifier recipientKeyIdentifier
pure $
PKESKPkt
(PKESKPayloadV6Packet (PKESKPayloadV6 recipientKeyIdentifier (toFVal pka) esk))
where
validateV6PKESKRecipientIdentifier :: BL.ByteString -> Either String ()
validateV6PKESKRecipientIdentifier rid =
case BL.length rid of
0 -> Right ()
20 -> Right ()
32 -> Right ()
21 -> validateVersionedFingerprint rid
33 -> validateVersionedFingerprint rid
ridLen ->
Left
("invalid PKESK v6 recipient identifier length: " ++
show ridLen ++
" (expected 0, 20, 21, 32, or 33)")
validateVersionedFingerprint :: BL.ByteString -> Either String ()
validateVersionedFingerprint rid =
let keyVersion = BL.head rid
fingerprintLen = BL.length (BL.tail rid)
in case keyVersion of
4 ->
if fingerprintLen == 20
then Right ()
else
Left
("PKESK v6 recipient identifier length/version mismatch: key version 4 requires fingerprint length 20, got " ++
show fingerprintLen)
6 ->
if fingerprintLen == 32
then Right ()
else
Left
("PKESK v6 recipient identifier length/version mismatch: key version 6 requires fingerprint length 32, got " ++
show fingerprintLen)
_ ->
Left
("invalid PKESK v6 recipient key version: " ++
show keyVersion ++ " (expected 4 or 6)")
getPkt' :: Word8 -> ByteOffset -> Get Pkt
getPkt' t len
| t == 1 = do
pv <- getWord8
body <- getRemainingLazyByteString
if pv == 6
then case parseV6PKESK body of
Right pkt -> return pkt
Left v6Err -> fail ("PKESK v6 parse failed: " ++ v6Err)
else case parseLegacyPKESK pv body of
Right pkt -> return pkt
Left legacyErr -> fail ("PKESK MPIs " ++ legacyErr)
| t == 2 = do
bs <- getRemainingLazyByteString
case runGetOrFail get bs of
Left (_, _, e) -> fail ("signature packet " ++ e)
Right (_, _, sp) -> return $ SignaturePkt sp
| t == 3 = do
pv <- getWord8
let getV6SKESKParams = do
symalgoWord <- getWord8
aeadWord <- getWord8
s2kLen <- getWord8
s2kBytes <- getLazyByteString (fromIntegral s2kLen)
s2k <-
case runGetOrFail getS2K s2kBytes of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing bytes in v6 SKESK S2K specifier"
| otherwise -> pure parsed
let symalgo = toFVal symalgoWord
aead = toFVal aeadWord
ivLen = fromIntegral (aeadNonceSize aead)
iv <- getLazyByteString ivLen
pure (symalgo, aead, s2k, iv)
case pv of
6 -> do
paramsLen <- getWord8
params <- getLazyByteString (fromIntegral paramsLen)
(symalgo, aead, s2k, iv) <-
case runGetOrFail getV6SKESKParams params of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing v6 SKESK parameters"
| otherwise -> pure parsed
payload <- getRemainingLazyByteString
when (BL.length payload < 16) $
fail "v6 SKESK payload must include encrypted session key and authentication tag"
let (esk, tag) = BL.splitAt (BL.length payload - 16) payload
return $
SKESKPkt
(SKESKPayloadV6Packet
(SKESKPayloadV6
symalgo
aead
s2k
iv
esk
tag))
4 -> do
symalgo <- getWord8
s2k <- getS2K
esk <- getRemainingLazyByteString
let mesk = if BL.null esk then Nothing else Just esk
validateV4SKESKEncryptedSessionKeyS2K s2k mesk
return $
SKESKPkt
(SKESKPayloadV4Packet
(SKESKPayloadV4
(toFVal symalgo)
s2k
mesk))
_ -> fail ("unsupported SKESK packet version " ++ show pv)
| t == 4 = do
pv <- getWord8
sigtype <- toFVal <$> getWord8
ha <- toFVal <$> getWord8
pka <- toFVal <$> getWord8
case pv of
3 -> do
skeyid <- getLazyByteString 8
nested <- getWord8 >>= parseOPSNestedFlag
return $
OnePassSignaturePkt
(OPSPayloadV3Packet
(OPSPayloadV3
pv
sigtype
ha
pka
(EightOctetKeyId skeyid)
nested))
6 -> do
saltSize <- getWord8
expectedSaltSize <-
maybe
(fail ("signature hash algorithm does not define a V6 salt size: " ++ show ha))
pure
(v6SaltSizeForHashAlgorithm ha)
when (saltSize /= expectedSaltSize) $
fail
("OPS v6 salt size mismatch for " ++
show ha ++ ": expected " ++ show expectedSaltSize ++ ", got " ++ show saltSize)
salt <- SignatureSalt <$> getLazyByteString (fromIntegral saltSize)
signerFingerprint <- getLazyByteString 32
nested <- getWord8 >>= parseOPSNestedFlag
return $
OnePassSignaturePkt
(OPSPayloadV6Packet
(OPSPayloadV6
sigtype
ha
pka
salt
signerFingerprint
nested))
_ -> fail ("Unsupported OPS version: " ++ show pv)
| t == 5 = do
bs <- getLazyByteString len
let ps =
flip runGetOrFail bs $ do
pkp <- getPKPayload
ska <- getSKAddendum pkp
return $ SecretKeyPkt pkp ska
case ps of
Left (_, _, err) -> fail ("secret key " ++ err)
Right (_, _, pkt) -> return pkt
| t == 6 = do
pkp <- getPKPayload
return $ PublicKeyPkt pkp
| t == 7 = do
bs <- getLazyByteString len
let ps =
flip runGetOrFail bs $ do
pkp <- getPKPayload
ska <- getSKAddendum pkp
return $ SecretSubkeyPkt pkp ska
case ps of
Left (_, _, err) -> fail ("secret subkey " ++ err)
Right (_, _, pkt) -> return pkt
| t == 8 = do
ca <- getWord8
cdata <- getLazyByteString (len - 1)
return $ CompressedDataPkt (toFVal ca) cdata
| t == 9 = do
sdata <- getLazyByteString len
return $ SymEncDataPkt sdata
| t == 10 = do
marker <- getLazyByteString len
return $ MarkerPkt marker
| t == 11 = do
dt <- getWord8
flen <- getWord8
fn <- getLazyByteString (fromIntegral flen)
ts <- fmap ThirtyTwoBitTimeStamp getWord32be
ldata <- getLazyByteString (len - (6 + fromIntegral flen))
return $ LiteralDataPkt (toFVal dt) fn ts ldata
| t == 12 = do
tdata <- getLazyByteString len
return $ TrustPkt tdata
| t == 13 = do
udata <- getByteString (fromIntegral len)
return . UserIdPkt . decodeUtf8With lenientDecode $ udata
| t == 14 = do
bs <- getLazyByteString len
let ps =
flip runGetOrFail bs $ do
pkp <- getPKPayload
return $ PublicSubkeyPkt pkp
case ps of
Left (_, _, err) -> fail ("public subkey " ++ err)
Right (_, _, pkt) -> return pkt
| t == 17 = do
bs <- getLazyByteString len
case runGetOrFail (many getUserAttrSubPacket) bs of
Left (_, _, err) -> fail ("user attribute " ++ err)
Right (_, _, uas) -> return $ UserAttributePkt uas
| t == 18 = do
pv <- getWord8
case pv of
1 -> do
b <- getLazyByteString (len - 1)
return $ SymEncIntegrityProtectedDataPkt (SEIPD1 pv b)
2 -> do
when (len < 36) $
fail "SEIPD v2 packet too short"
symalgo <- toFVal <$> getWord8
aeadalgo <- toFVal <$> getWord8
chunkSize <- getWord8
salt <- Salt <$> getByteString 32
encrypted <- getLazyByteString (len - 36)
validateSEIPDv2Header symalgo aeadalgo chunkSize encrypted
return $
SymEncIntegrityProtectedDataPkt
(SEIPD2
symalgo
aeadalgo
chunkSize
salt
encrypted)
_ -> fail ("Unsupported SEIPD version: " ++ show pv)
| t == 19 = do
hash <- getLazyByteString 20
return $ ModificationDetectionCodePkt hash
| otherwise = do
payload <- getLazyByteString len
return $ OtherPacketPkt t payload
getUserAttrSubPacket :: Get UserAttrSubPacket
getUserAttrSubPacket = do
l <- fmap fromIntegral getSubPacketLength
t <- getWord8
getUserAttrSubPacket' t l
where
getUserAttrSubPacket' :: Word8 -> ByteOffset -> Get UserAttrSubPacket
getUserAttrSubPacket' t l
| t == 1 = do
_ <- getWord16le -- ihlen
hver <- getWord8 -- should be 1
iformat <- getWord8
nuls <- getLazyByteString 12 -- should be NULs
bs <- getLazyByteString (l - 17)
if hver /= 1 || nuls /= BL.pack (replicate 12 0)
then fail "Corrupt UAt subpacket"
else return $ ImageAttribute (ImageHV1 (toFVal iformat)) bs
| otherwise = do
bs <- getLazyByteString (l - 1)
return $ OtherUASub t bs
putUserAttrSubPacket :: UserAttrSubPacket -> Put
putUserAttrSubPacket ua = do
let sp = runPut $ putUserAttrSubPacket' ua
putSubPacketLength . fromIntegral . BL.length $ sp
putLazyByteString sp
where
putUserAttrSubPacket' (ImageAttribute (ImageHV1 iformat) idata) = do
putWord8 1
putWord16le 16
putWord8 1
putWord8 (fromFVal iformat)
replicateM_ 12 $ putWord8 0
putLazyByteString idata
putUserAttrSubPacket' (OtherUASub t bs) = do
putWord8 t
putLazyByteString bs
-- | Serialize PKESKv3 session-key material.
-- For ECDH and X25519 the RFC 6637 §8 / RFC 9580 §5.1.6 wire format is used:
-- MPI(ephemeral_key) || 1-octet-count || wrapped_session_key_bytes.
-- All other algorithms use the standard MPI sequence.
putPKESKv3SessionKeyMaterial :: PubKeyAlgorithm -> NE.NonEmpty MPI -> Put
putPKESKv3SessionKeyMaterial pka mpis
| pka `elem` [ECDH, X25519]
, (ephMPI NE.:| [wrappedMPI]) <- mpis = do
put ephMPI
let rawWrapped = i2osp (unMPI wrappedMPI)
-- Left-pad to the nearest valid RFC 3394 wrapped-key length so that
-- leading-zero bytes stripped by i2osp are restored.
targetLen = headDef (B.length rawWrapped) (filter (>= B.length rawWrapped) [32, 40, 48])
paddedWrapped = leftPadTo targetLen rawWrapped
putWord8 (fromIntegral (B.length paddedWrapped))
putByteString paddedWrapped
| otherwise = F.mapM_ put mpis
where
headDef d [] = d
headDef _ (x:_) = x
putPkt :: Pkt -> Put
putPkt (PKESKPkt (PKESKPayloadV3Packet (PKESKPayloadV3 pv eokeyid pka mpis))) = do
putWord8 (0xc0 .|. 1)
let bsk = runPut $ putPKESKv3SessionKeyMaterial pka mpis
putPacketLength . fromIntegral $ 10 + BL.length bsk
putWord8 pv -- must be 3
putLazyByteString (unEOKI eokeyid) -- must be 8 octets
putWord8 $ fromIntegral . fromFVal $ pka
putLazyByteString bsk
putPkt (PKESKPkt (PKESKPayloadV6Packet (PKESKPayloadV6 recipientKeyIdentifier pka esk))) = do
putWord8 (0xc0 .|. 1)
let keyIdentifierLen = BL.length recipientKeyIdentifier
when (keyIdentifierLen > 255) $
error "PKESK v6 recipient key identifier must fit in one octet"
putPacketLength . fromIntegral $ 3 + keyIdentifierLen + BL.length esk
putWord8 6
putWord8 (fromIntegral keyIdentifierLen)
putLazyByteString recipientKeyIdentifier
putWord8 $ fromIntegral . fromFVal $ pka
putLazyByteString esk
putPkt (SignaturePkt sp) = do
putWord8 (0xc0 .|. 2)
let bs = runPut $ put sp
putLengthThenPayload bs
putPkt (SKESKPkt (SKESKPayloadV4Packet (SKESKPayloadV4 symalgo s2k mesk))) = do
putWord8 (0xc0 .|. 3)
let bs2k = fromS2K s2k
let bsk = fromMaybe BL.empty mesk
putPacketLength . fromIntegral $ 2 + BL.length bs2k + BL.length bsk
putWord8 4
putWord8 $ fromIntegral . fromFVal $ symalgo
putLazyByteString bs2k
putLazyByteString bsk
putPkt (SKESKPkt (SKESKPayloadV6Packet (SKESKPayloadV6 symalgo aead s2k iv esk tag))) = do
putWord8 (0xc0 .|. 3)
let bs2k = fromS2K s2k
let params =
BL.pack
[ fromIntegral (fromFVal symalgo)
, fromIntegral (fromFVal aead)
, fromIntegral (BL.length bs2k)
] <>
bs2k <> iv
putPacketLength . fromIntegral $ 2 + BL.length params + BL.length esk + BL.length tag
putWord8 6
putWord8 (fromIntegral (BL.length params))
putLazyByteString params
putLazyByteString esk
putLazyByteString tag
putPkt (OnePassSignaturePkt (OPSPayloadV3Packet (OPSPayloadV3 pv sigtype ha pka skeyid nested))) = do
putWord8 (0xc0 .|. 4)
let bs =
runPut $ do
putWord8 pv -- should be 3
putWord8 $ fromIntegral . fromFVal $ sigtype
putWord8 $ fromIntegral . fromFVal $ ha
putWord8 $ fromIntegral . fromFVal $ pka
putLazyByteString (unEOKI skeyid)
putWord8 . fromIntegral . fromEnum $ not nested
putLengthThenPayload bs
putPkt (OnePassSignaturePkt (OPSPayloadV6Packet (OPSPayloadV6 sigtype ha pka salt signerFingerprint nested))) = do
putWord8 (0xc0 .|. 4)
let saltBytes = unSignatureSalt salt
saltSize = BL.length saltBytes
expectedSaltSize =
maybe
(error ("signature hash algorithm does not define a V6 salt size: " ++ show ha))
id
(v6SaltSizeForHashAlgorithm ha)
when (fromIntegral saltSize /= expectedSaltSize) $
error
("OPS v6 salt size mismatch for " ++
show ha ++ ": expected " ++ show expectedSaltSize ++ ", got " ++ show saltSize)
when (BL.length signerFingerprint /= 32) $
error "OPS v6 signer fingerprint must be exactly 32 octets"
let bs =
runPut $ do
putWord8 6
putWord8 $ fromIntegral . fromFVal $ sigtype
putWord8 $ fromIntegral . fromFVal $ ha
putWord8 $ fromIntegral . fromFVal $ pka
putWord8 (fromIntegral saltSize)
putLazyByteString saltBytes
putLazyByteString signerFingerprint
putWord8 . fromIntegral . fromEnum $ not nested
putLengthThenPayload bs
putPkt (SecretKeyPkt pkp ska) = do
putWord8 (0xc0 .|. 5)
let bs = runPut (putPKPayload pkp >> putSKAddendumForPKPayload pkp ska)
putLengthThenPayload bs
putPkt (PublicKeyPkt pkp) = do
putWord8 (0xc0 .|. 6)
let bs = runPut $ putPKPayload pkp
putLengthThenPayload bs
putPkt (SecretSubkeyPkt pkp ska) = do
putWord8 (0xc0 .|. 7)
let bs = runPut (putPKPayload pkp >> putSKAddendumForPKPayload pkp ska)
putLengthThenPayload bs
putPkt (CompressedDataPkt ca cdata) = do
putWord8 (0xc0 .|. 8)
let bs =
runPut $ do
putWord8 $ fromIntegral . fromFVal $ ca
putLazyByteString cdata
putLengthThenPayload bs
putPkt (SymEncDataPkt b) = do
putWord8 (0xc0 .|. 9)
putLengthThenPayload b
putPkt (MarkerPkt b) = do
putWord8 (0xc0 .|. 10)
putLengthThenPayload b
putPkt (LiteralDataPkt dt fn ts b) = do
putWord8 (0xc0 .|. 11)
let bs =
runPut $ do
putWord8 $ fromIntegral . fromFVal $ dt
putWord8 $ fromIntegral . BL.length $ fn
putLazyByteString fn
putWord32be . unThirtyTwoBitTimeStamp $ ts
putLazyByteString b
putLengthThenPayload bs
putPkt (TrustPkt b) = do
putWord8 (0xc0 .|. 12)
putLengthThenPayload b
putPkt (UserIdPkt u) = do
putWord8 (0xc0 .|. 13)
let bs = encodeUtf8 u
putPacketLength . fromIntegral $ B.length bs
putByteString bs
putPkt (PublicSubkeyPkt pkp) = do
putWord8 (0xc0 .|. 14)
let bs = runPut $ putPKPayload pkp
putLengthThenPayload bs
putPkt (UserAttributePkt us) = do
putWord8 (0xc0 .|. 17)
let bs = runPut $ mapM_ put us
putLengthThenPayload bs
putPkt (SymEncIntegrityProtectedDataPkt (SEIPD1 pv b)) = do
putWord8 (0xc0 .|. 18)
putPacketLength . fromIntegral $ BL.length b + 1
putWord8 pv -- should be 1
putLazyByteString b
putPkt (SymEncIntegrityProtectedDataPkt (SEIPD2 symalgo aeadalgo chunkSize salt b)) = do
when (B.length (unSalt salt) /= 32) $
error "SEIPD v2 salt must be exactly 32 octets"
when (chunkSize > 16) $
error "SEIPD v2 chunk size octet must be between 0 and 16"
case symalgo of
OtherSA _ -> error "SEIPD v2 requires a known symmetric algorithm"
Plaintext -> error "SEIPD v2 cannot use plaintext cipher"
_ -> return ()
case aeadalgo of
OtherAEADAlgo _ -> error "SEIPD v2 requires a known AEAD algorithm"
_ -> return ()
putWord8 (0xc0 .|. 18)
putPacketLength . fromIntegral $ BL.length b + 36
putWord8 2
putWord8 (fromFVal symalgo)
putWord8 (fromFVal aeadalgo)
putWord8 chunkSize
putByteString (unSalt salt)
putLazyByteString b
putPkt (ModificationDetectionCodePkt hash) = do
putWord8 (0xc0 .|. 19)
putLengthThenPayload hash
putPkt (OtherPacketPkt t payload) = do
when (t > 63) $
error ("cannot serialize OtherPacket packet tag > 63: " ++ show t)
putWord8 (0xc0 .|. t)
putLengthThenPayload payload
putPkt (BrokenPacketPkt _ t payload) = putPkt (OtherPacketPkt t payload)
-- | Validate a packet before serialization to catch constraint violations early.
-- Returns Left with descriptive error if validation fails.
validatePkt :: Pkt -> Either String ()
validatePkt (PKESKPkt (PKESKPayloadV6Packet (PKESKPayloadV6 recipientKeyIdentifier _ _))) = do
let keyIdentifierLen = BL.length recipientKeyIdentifier
when (keyIdentifierLen > 255) $
Left "PKESK v6 recipient key identifier must fit in one octet (max 255 bytes)"
Right ()
validatePkt (OnePassSignaturePkt (OPSPayloadV6Packet (OPSPayloadV6 _ ha _ salt signerFingerprint _))) = do
let saltBytes = unSignatureSalt salt
saltSize = BL.length saltBytes
expectedSaltSize <-
case v6SaltSizeForHashAlgorithm ha of
Nothing -> Left $ "signature hash algorithm does not define a V6 salt size: " ++ show ha
Just sz -> Right sz
when (fromIntegral saltSize /= expectedSaltSize) $
Left
("OPS v6 salt size mismatch for " ++
show ha ++ ": expected " ++ show expectedSaltSize ++ ", got " ++ show saltSize)
when (BL.length signerFingerprint /= 32) $
Left "OPS v6 signer fingerprint must be exactly 32 octets"
Right ()
validatePkt (SymEncIntegrityProtectedDataPkt (SEIPD2 symalgo aeadalgo chunkSize salt _)) = do
when (B.length (unSalt salt) /= 32) $
Left "SEIPD v2 salt must be exactly 32 octets"
when (chunkSize > 16) $
Left "SEIPD v2 chunk size octet must be between 0 and 16"
case symalgo of
OtherSA _ -> Left "SEIPD v2 requires a known symmetric algorithm"
Plaintext -> Left "SEIPD v2 cannot use plaintext cipher"
_ -> Right ()
case aeadalgo of
OtherAEADAlgo _ -> Left "SEIPD v2 requires a known AEAD algorithm"
_ -> Right ()
validatePkt (OtherPacketPkt t _) = do
when (t > 63) $
Left ("cannot serialize OtherPacket packet tag > 63: " ++ show t)
Right ()
validatePkt _ = Right ()
-- | Serialize a packet with explicit validation and error handling.
-- Validates constraints before calling putPkt to ensure errors are caught early.
putPktEither :: Pkt -> Either String Put
putPktEither pkt = case validatePkt pkt of
Left err -> Left err
Right () -> Right (putPkt pkt)
putLengthThenPayload :: ByteString -> Put
putLengthThenPayload bs = do
let len = BL.length bs
if len < fromIntegral (0x100000000 :: Integer)
then do
putPacketLength (fromIntegral len)
putLazyByteString bs
else putPartialLengthPayload bs
where
maxPartialChunkSize :: Int64
maxPartialChunkSize = 1 `shiftL` (30 :: Int)
putPartialLengthPayload :: ByteString -> Put
putPartialLengthPayload payload
| BL.length payload > maxPartialChunkSize = do
let (chunk, rest) = BL.splitAt maxPartialChunkSize payload
putPartialLength 30
putLazyByteString chunk
putPartialLengthPayload rest
| otherwise = do
putPacketLength (fromIntegral (BL.length payload))
putLazyByteString payload
validateSEIPDv2Header ::
SymmetricAlgorithm -> AEADAlgorithm -> Word8 -> ByteString -> Get ()
validateSEIPDv2Header symalgo aeadalgo chunkSize encrypted = do
when (chunkSize > 16) $
fail "SEIPD v2 chunk size octet must be between 0 and 16"
when (BL.null encrypted) $
fail "SEIPD v2 payload is missing encrypted data and final authentication tag"
case symalgo of
OtherSA _ -> fail "SEIPD v2 requires a known symmetric algorithm"
Plaintext -> fail "SEIPD v2 cannot use plaintext cipher"
_ -> return ()
case aeadalgo of
OtherAEADAlgo _ -> fail "SEIPD v2 requires a known AEAD algorithm"
_ -> return ()
getMPI :: Get MPI
getMPI = do
mpilen <- getWord16be
bs <- getByteString (fromIntegral (mpilen + 7) `div` 8)
return $ MPI (os2ip bs)
getPubkey :: PubKeyAlgorithm -> Get PKey
getPubkey RSA = do
MPI n <- get
MPI e <- get
return $
RSAPubKey
(RSA_PublicKey (R.PublicKey (fromIntegral . B.length . i2osp $ n) n e))
getPubkey DeprecatedRSAEncryptOnly = getPubkey RSA
getPubkey DeprecatedRSASignOnly = getPubkey RSA
getPubkey DSA = do
MPI p <- get
MPI q <- get
MPI g <- get
MPI y <- get
return $ DSAPubKey (DSA_PublicKey (D.PublicKey (D.Params p g q) y))
getPubkey ElgamalEncryptOnly = getPubkey ForbiddenElgamal
getPubkey ForbiddenElgamal = do
MPI p <- get
MPI g <- get
MPI y <- get
return $ ElGamalPubKey p g y
getPubkey ECDSA = do
curvelength <- getWord8
when (curvelength == 0 || curvelength == 0xff) $
fail "invalid ECC curve OID length octet (reserved value)"
curveoid <- getByteString (fromIntegral curvelength)
MPI mpi <- getMPI
case curveoidBSToCurve curveoid of
Left e -> fail e
Right Curve25519 ->
EdDSAPubKey P.Ed25519 <$> (PrefixedNativeEPoint <$> validatePrefixedNativePoint 32 "Curve25519Legacy" mpi)
Right curve ->
case bs2Point (i2osp mpi) of
Left e -> fail e
Right point ->
return . ECDSAPubKey . ECDSA_PublicKey .
ECDSA.PublicKey (curve2Curve curve) $
point
getPubkey ECDH = do
ed <- getPubkey ECDSA -- could be an ECDSA or an EdDSA
kdflen <- getWord8
when (kdflen == 0 || kdflen == 0xff) $
fail "invalid ECDH KDF field length octet (reserved value)"
when (kdflen /= 3) $
fail ("invalid ECDH KDF field length: " ++ show kdflen)
one <- getWord8
when (one /= 1) $
fail ("invalid ECDH KDF reserved octet: " ++ show one)
kdfHA <- get
kdfSA <- get
return $ ECDHPubKey ed kdfHA kdfSA
getPubkey EdDSA = do
curvelength <- getWord8
when (curvelength == 0 || curvelength == 0xff) $
fail "invalid EdDSA curve OID length octet (reserved value)"
curveoid <- getByteString (fromIntegral curvelength)
MPI mpi <- getMPI
case curveoidBSToEdSigningCurve curveoid of
Left e -> fail e
Right P.Ed25519 ->
EdDSAPubKey P.Ed25519 <$> (PrefixedNativeEPoint <$> validatePrefixedNativePoint 32 "Ed25519Legacy" mpi)
Right P.Ed448 ->
EdDSAPubKey P.Ed448 <$> (PrefixedNativeEPoint <$> validatePrefixedNativePoint 57 "Ed448Legacy" mpi)
getPubkey pka | pka == BTypes.Ed25519 =
parseFixedLengthOrLegacyPubkey
32
(EdDSAPubKey P.Ed25519 . NativeEPoint . EPoint . os2ip . BL.toStrict)
(getPubkey EdDSA)
getPubkey pka | pka == BTypes.Ed448 =
parseFixedLengthOrLegacyPubkey
57
(EdDSAPubKey P.Ed448 . NativeEPoint . EPoint . os2ip . BL.toStrict)
(getPubkey EdDSA)
getPubkey X25519 =
parseFixedLengthOrLegacyPubkey
32
(EdDSAPubKey P.Ed25519 . NativeEPoint . EPoint . os2ip . BL.toStrict)
(getPubkey ECDH)
getPubkey X448 =
parseFixedLengthOrLegacyPubkey
56
(EdDSAPubKey P.Ed448 . NativeEPoint . EPoint . os2ip . BL.toStrict)
(getPubkey ECDH)
getPubkey _ = UnknownPKey <$> getRemainingLazyByteString
parseFixedLengthOrLegacyPubkey :: Int64 -> (BL.ByteString -> PKey) -> Get PKey -> Get PKey
parseFixedLengthOrLegacyPubkey expectedLen decodeFixed legacyParser = do
remaining <- lookAhead getRemainingLazyByteString
if BL.length remaining == expectedLen
then decodeFixed <$> getLazyByteString expectedLen
else legacyParser
getPubkeyV6 :: PubKeyAlgorithm -> Get PKey
getPubkeyV6 pka
| pka == BTypes.Ed25519 = do
len <- getWord32be
bs <- getByteString (fromIntegral len)
when (B.length bs /= 32) $
fail "invalid v6 Ed25519 public key length"
return $ EdDSAPubKey P.Ed25519 (NativeEPoint (EPoint (os2ip bs)))
| pka == BTypes.Ed448 = do
len <- getWord32be
bs <- getByteString (fromIntegral len)
when (B.length bs /= 57) $
fail "invalid v6 Ed448 public key length"
return $ EdDSAPubKey P.Ed448 (NativeEPoint (EPoint (os2ip bs)))
| pka == BTypes.X25519 = do
len <- getWord32be
bs <- getByteString (fromIntegral len)
when (B.length bs /= 32) $
fail "invalid v6 X25519 public key length"
return $ EdDSAPubKey P.Ed25519 (NativeEPoint (EPoint (os2ip bs)))
| pka == BTypes.X448 = do
len <- getWord32be
bs <- getByteString (fromIntegral len)
when (B.length bs /= 56) $
fail "invalid v6 X448 public key length"
return $ EdDSAPubKey P.Ed448 (NativeEPoint (EPoint (os2ip bs)))
| otherwise = getPubkey pka
bs2Point :: B.ByteString -> Either String ECDSA.PublicPoint
bs2Point bs =
if B.null bs
then Left "empty EC point encoding"
else
let xy = B.drop 1 bs
l = B.length xy
in if B.head bs /= 0x04
then Left $ "unknown type of point: " ++ show (B.unpack bs)
else if odd l
then Left "malformed EC point encoding: odd coordinate payload length"
else
return
(uncurry
ECCT.Point
((os2ip *** os2ip) (B.splitAt (div l 2) xy)))
putPubkey :: PKey -> Put
putPubkey (UnknownPKey bs) = putLazyByteString bs
putPubkey p@(ECDSAPubKey (ECDSA_PublicKey (ECDSA.PublicKey curve _))) =
let Right curveoidbs = curveToCurveoidBS (curveFromCurve curve)
in putCurveOID curveoidbs >>
mapM_ put (pubkeyToMPIs p)
putPubkey p@(ECDHPubKey (ECDSAPubKey (ECDSA_PublicKey (ECDSA.PublicKey curve _))) kha ksa) =
let Right curveoidbs = curveToCurveoidBS (curveFromCurve curve)
in putCurveOID curveoidbs >>
mapM_ put (pubkeyToMPIs p) >>
putECDHKDFParams kha ksa
putPubkey p@(ECDHPubKey (EdDSAPubKey curve (PrefixedNativeEPoint _)) kha ksa) =
let Right curveoidbs = curveToCurveoidBS (ed2ec curve)
in putCurveOID curveoidbs >>
mapM_ put (pubkeyToMPIs p) >>
putECDHKDFParams kha ksa
where
ed2ec P.Ed25519 = Curve25519
ed2ec P.Ed448 = Curve448
putPubkey p@(EdDSAPubKey curve (PrefixedNativeEPoint _)) =
let Right curveoidbs = edSigningCurveToCurveoidBS curve
in putCurveOID curveoidbs >>
mapM_ put (pubkeyToMPIs p)
putPubkey (ECDHPubKey (EdDSAPubKey curve (NativeEPoint _)) _ _) =
error ("legacy ECDH serialization requires a prefixed-native " ++ show curve ++ " point")
putPubkey (EdDSAPubKey curve (NativeEPoint _)) =
error ("legacy EdDSA serialization requires a prefixed-native " ++ show curve ++ " point")
putPubkey p = mapM_ put (pubkeyToMPIs p)
putPubkeyV6 :: PKey -> Put
putPubkeyV6 (EdDSAPubKey P.Ed25519 (NativeEPoint (EPoint x))) = do
let bs = fixedLengthOctets 32 x
putWord32be . fromIntegral . B.length $ bs
putByteString bs
putPubkeyV6 (EdDSAPubKey P.Ed448 (NativeEPoint (EPoint x))) = do
let bs = fixedLengthOctets 57 x
putWord32be . fromIntegral . B.length $ bs
putByteString bs
putPubkeyV6 (ECDHPubKey (EdDSAPubKey P.Ed25519 (NativeEPoint (EPoint x))) kha ksa) = do
let bs = fixedLengthOctets 32 x
putWord32be . fromIntegral . B.length $ bs
putByteString bs
put kha
put ksa
putPubkeyV6 (ECDHPubKey (EdDSAPubKey P.Ed448 (NativeEPoint (EPoint x))) kha ksa) = do
let bs = fixedLengthOctets 56 x
putWord32be . fromIntegral . B.length $ bs
putByteString bs
put kha
put ksa
putPubkeyV6 p = putPubkey p
fixedLengthOctets :: Int -> Integer -> B.ByteString
fixedLengthOctets targetLen x =
let bs = i2osp x
in if B.length bs > targetLen
then error ("public key element does not fit in " ++ show targetLen ++ " octets")
else B.replicate (targetLen - B.length bs) 0 <> bs
validatePrefixedNativePoint :: Int -> String -> Integer -> Get EPoint
validatePrefixedNativePoint targetLen label i =
let bs = i2osp i
in if B.length bs /= targetLen + 1
then
fail
("invalid " ++ label ++ " public key length: expected " ++
show (targetLen + 1) ++ " octets with 0x40 prefix, got " ++ show (B.length bs))
else
if B.head bs /= 0x40
then fail ("invalid " ++ label ++ " public key: missing 0x40 prefix")
else pure (EPoint i)
putCurveOID :: B.ByteString -> Put
putCurveOID oid = do
let oidLength = B.length oid
when (oidLength == 0 || oidLength == 0xff) $
error "curve OID length cannot use reserved values 0 or 255"
putWord8 (fromIntegral oidLength)
putByteString oid
putECDHKDFParams :: HashAlgorithm -> SymmetricAlgorithm -> Put
putECDHKDFParams kdfHA kdfSA = do
let kdfLengthOctet = 0x03
when (kdfLengthOctet == 0 || kdfLengthOctet == 0xff) $
error "ECDH KDF field length cannot use reserved values 0 or 255"
putWord8 kdfLengthOctet
putWord8 0x01
put kdfHA
put kdfSA
parseOPSNestedFlag :: Word8 -> Get NestedFlag
parseOPSNestedFlag 0 = pure True
parseOPSNestedFlag 1 = pure False
parseOPSNestedFlag other =
fail ("invalid OPS nested flag octet: " ++ show other)
getSecretKey :: SomePKPayload -> Get SKey
getSecretKey pkp
| _pkalgo pkp `elem` [RSA, DeprecatedRSAEncryptOnly, DeprecatedRSASignOnly] = do
MPI d <- get
MPI p <- get
MPI q <- get
MPI _ <- get -- u
case inverse q p of
Nothing -> fail "invalid RSA secret key: q has no inverse modulo p"
Just qinv -> do
let dP = d `mod` (p - 1)
dQ = d `mod` (q - 1)
pub = (\(RSAPubKey (RSA_PublicKey x)) -> x) (_pubkey pkp)
return $ RSAPrivateKey (RSA_PrivateKey (R.PrivateKey pub d p q dP dQ qinv))
| _pkalgo pkp == DSA = do
MPI x <- get
return $ DSAPrivateKey (DSA_PrivateKey (D.PrivateKey (D.Params 0 0 0) x))
| _pkalgo pkp `elem` [ElgamalEncryptOnly, ForbiddenElgamal] = do
MPI x <- get
return $ ElGamalPrivateKey x
| _pkalgo pkp == ECDSA = do
let pubcurve =
(\(ECDSAPubKey (ECDSA_PublicKey p)) -> ECDSA.public_curve p)
(_pubkey pkp)
getECDSAScalarPrivateKey pubcurve
| _pkalgo pkp == ECDH
= do
pubcurve <- ecdhPrivateCurveFromPKPayload pkp
getECDHScalarPrivateKey pubcurve
| _pkalgo pkp == X25519 = do
if _keyVersion pkp == V6
then do
sk <- getByteString 32
return $ X25519PrivateKey sk
else do
pubcurve <- ecdhPrivateCurveFromPKPayload pkp
getECDHScalarPrivateKey pubcurve
| _pkalgo pkp == X448 = do
if _keyVersion pkp == V6
then do
sk <- getByteString 56
return $ X448PrivateKey sk
else UnknownSKey <$> getRemainingLazyByteString
| _pkalgo pkp == EdDSA = do
if _keyVersion pkp == V6
then do
case _pubkey pkp of
EdDSAPubKey P.Ed25519 _ -> EdDSAPrivateKey P.Ed25519 <$> getByteString 32
EdDSAPubKey P.Ed448 _ -> EdDSAPrivateKey P.Ed448 <$> getByteString 57
_ -> UnknownSKey <$> getRemainingLazyByteString
else do
MPI x <- get
case _pubkey pkp of
EdDSAPubKey P.Ed25519 _ ->
return $ EdDSAPrivateKey P.Ed25519 (leftPadTo 32 (i2osp x))
EdDSAPubKey P.Ed448 _ ->
return $ EdDSAPrivateKey P.Ed448 (leftPadTo 57 (i2osp x))
_ -> return $ UnknownSKey (BL.fromStrict (i2osp x))
| otherwise = UnknownSKey <$> getRemainingLazyByteString
getECDSAScalarPrivateKey :: ECCT.Curve -> Get SKey
getECDSAScalarPrivateKey curve = do
MPI pn <- get
pure $ ECDSAPrivateKey (ECDSA_PrivateKey (ECDSA.PrivateKey curve pn))
getECDHScalarPrivateKey :: ECCT.Curve -> Get SKey
getECDHScalarPrivateKey curve = do
MPI pn <- get
pure $ ECDHPrivateKey (ECDSA_PrivateKey (ECDSA.PrivateKey curve pn))
ecdhPrivateCurveFromPKPayload :: SomePKPayload -> Get ECCT.Curve
ecdhPrivateCurveFromPKPayload pkp =
case _pubkey pkp of
ECDHPubKey (ECDSAPubKey (ECDSA_PublicKey p)) _ _ ->
pure (ECDSA.public_curve p)
ECDHPubKey (EdDSAPubKey P.Ed25519 _) _ _ ->
pure (curve2Curve Curve25519)
ECDHPubKey (EdDSAPubKey P.Ed448 _) _ _ ->
pure (curve2Curve Curve448)
other ->
fail
("ECDH/X25519 secret key requires an ECDH public key packet, got " ++
show other)
putSKey :: SKey -> Either String Put
putSKey (RSAPrivateKey (RSA_PrivateKey (R.PrivateKey _ d p q _ _ _))) =
case inverse q p of
Just u ->
Right (put (MPI d) >> put (MPI p) >> put (MPI q) >> put (MPI u))
Nothing ->
Left
"putSKey: invalid RSA key — q has no multiplicative inverse mod p (key is mathematically broken)"
putSKey (DSAPrivateKey (DSA_PrivateKey (D.PrivateKey _ x))) =
Right (put (MPI x))
putSKey (ElGamalPrivateKey x) =
Right (put (MPI x))
putSKey (ECDHPrivateKey (ECDSA_PrivateKey (ECDSA.PrivateKey _ d))) =
Right (put (MPI d))
putSKey (ECDSAPrivateKey (ECDSA_PrivateKey (ECDSA.PrivateKey _ d))) =
Right (put (MPI d))
putSKey (EdDSAPrivateKey P.Ed25519 sk) = Right (putByteString sk)
putSKey (EdDSAPrivateKey P.Ed448 sk) = Right (putByteString sk)
putSKey (X25519PrivateKey sk) = Right (putByteString sk)
putSKey (X448PrivateKey sk) = Right (putByteString sk)
putSKey (UnknownSKey bs) = Right (putLazyByteString bs)
putSKeyForPKPayload :: SomePKPayload -> SKey -> Either String Put
putSKeyForPKPayload pkp sk@(EdDSAPrivateKey _ bs)
| _keyVersion pkp == V6 = putSKey sk
| otherwise = Right (put (MPI (os2ip bs)))
putSKeyForPKPayload _ sk = putSKey sk
putMPI :: MPI -> Put
putMPI (MPI i) = do
let bs = i2osp i
putWord16be . fromIntegral . numBits $ i
putByteString bs
data PKPayloadReadCase where
PKPayloadReadCaseV3 :: V3Expiration -> PubKeyAlgorithm -> PKPayloadReadCase
PKPayloadReadCaseV4 :: PubKeyAlgorithm -> PKPayloadReadCase
PKPayloadReadCaseV6 :: PubKeyAlgorithm -> PKPayloadReadCase
pkPayloadReadCase :: Word8 -> Get PKPayloadReadCase
pkPayloadReadCase version =
case version of
2 -> do
v3e <- getWord16be
pka <- get
pure (PKPayloadReadCaseV3 v3e pka)
3 -> do
v3e <- getWord16be
pka <- get
pure (PKPayloadReadCaseV3 v3e pka)
4 -> PKPayloadReadCaseV4 <$> get
6 -> PKPayloadReadCaseV6 <$> get
_ -> fail ("unsupported key packet version " ++ show version)
getPKPayload :: Get SomePKPayload
getPKPayload = do
version <- getWord8
ctime <- fmap ThirtyTwoBitTimeStamp getWord32be
readCase <- pkPayloadReadCase version
case readCase of
PKPayloadReadCaseV3 v3e pka -> do
pk <- getPubkey pka
pure $! PKPayload DeprecatedV3 ctime v3e pka pk
PKPayloadReadCaseV4 pka -> do
pk <- getPubkey pka
pure $! PKPayload V4 ctime 0 pka pk
PKPayloadReadCaseV6 pka -> do
pk <- getPubkeyV6 pka
pure $! PKPayload V6 ctime 0 pka pk
data PKPayloadWriteCase where
PKPayloadWriteCaseV3 :: PKPayload 'DeprecatedV3 -> PKPayloadWriteCase
PKPayloadWriteCaseV4 :: PKPayload 'V4 -> PKPayloadWriteCase
PKPayloadWriteCaseV6 :: PKPayload 'V6 -> PKPayloadWriteCase
pkPayloadWriteCase :: SomePKPayload -> PKPayloadWriteCase
pkPayloadWriteCase (SomePKPayload pkp) =
case pkp of
PKPayloadV3 {} -> PKPayloadWriteCaseV3 pkp
PKPayloadV4 {} -> PKPayloadWriteCaseV4 pkp
PKPayloadV6 {} -> PKPayloadWriteCaseV6 pkp
putPKPayload :: SomePKPayload -> Put
putPKPayload pkpSome =
case pkPayloadWriteCase pkpSome of
PKPayloadWriteCaseV3 (PKPayloadV3 ctime v3e pka pk) -> do
putWord8 3
putWord32be . unThirtyTwoBitTimeStamp $ ctime
putWord16be v3e
put pka
putPubkey pk
PKPayloadWriteCaseV4 (PKPayloadV4 ctime pka pk) -> do
putWord8 4
putWord32be . unThirtyTwoBitTimeStamp $ ctime
put pka
putPubkeyV4ForAlgorithm pka pk
PKPayloadWriteCaseV6 (PKPayloadV6 ctime pka pk) -> do
putWord8 6
putWord32be . unThirtyTwoBitTimeStamp $ ctime
put pka
putPubkeyV6 pk
putPubkeyV4ForAlgorithm :: PubKeyAlgorithm -> PKey -> Put
putPubkeyV4ForAlgorithm pka pk
| pka == BTypes.Ed25519 = putPubkeyV4Fixed 32 P.Ed25519 pk
| pka == BTypes.Ed448 = putPubkeyV4Fixed 57 P.Ed448 pk
| pka == BTypes.X25519 = putPubkeyV4Fixed 32 P.Ed25519 pk
| pka == BTypes.X448 = putPubkeyV4Fixed 56 P.Ed448 pk
| otherwise = putPubkey pk
putPubkeyV4Fixed :: Int -> P.EdSigningCurve -> PKey -> Put
putPubkeyV4Fixed targetLen expectedCurve (EdDSAPubKey curve (NativeEPoint (EPoint x)))
| curve == expectedCurve = putByteString (fixedLengthOctets targetLen x)
putPubkeyV4Fixed _ _ pk = putPubkey pk
getSKAddendum :: SomePKPayload -> Get SKAddendum
getSKAddendum (SomePKPayload pkp) =
toSKAddendum <$> getSKAddendumTyped pkp
getSKAddendumTyped :: PKPayload v -> Get (SKAddendumV v)
getSKAddendumTyped pkp = do
s2kusage <- getWord8
let pkpSome = SomePKPayload pkp
getLegacyS2KProtected constructor = do
symencWord <- getWord8
s2k <- getS2K
let symenc = toFVal symencWord
case s2k of
OtherS2K _ _ -> return $ constructor symenc s2k mempty BL.empty
_ -> do
blockSize <- either fail pure (symEncBlockSize symenc)
iv <- IV <$> getByteString blockSize
encryptedblock <- getRemainingLazyByteString
return $ constructor symenc s2k iv encryptedblock
case s2kusage of
0 ->
case pkp of
PKPayloadV6 {} -> do
sk <- getSecretKey pkpSome
return (SKAUnencryptedV6 sk)
PKPayloadV3 {} -> do
rest <- lookAhead getRemainingLazyByteString
secretLen <-
case runGetOrFail
(do
start <- bytesRead
_ <- getSecretKey pkpSome
end <- bytesRead
pure (end - start))
rest of
Left (_, _, err) -> fail err
Right (_, _, len) -> pure len
sk <- getSecretKey pkpSome
checksum <- getWord16be
let expectedChecksum =
checksum16Bytes (BL.toStrict (BL.take secretLen rest))
when (checksum /= expectedChecksum) $
fail
("legacy unencrypted secret-key checksum mismatch: expected " ++
show expectedChecksum ++ ", got " ++ show checksum)
return (SKAUnencryptedLegacy sk checksum)
PKPayloadV4 {} -> do
rest <- lookAhead getRemainingLazyByteString
secretLen <-
case runGetOrFail
(do
start <- bytesRead
_ <- getSecretKey pkpSome
end <- bytesRead
pure (end - start))
rest of
Left (_, _, err) -> fail err
Right (_, _, len) -> pure len
sk <- getSecretKey pkpSome
checksum <- getWord16be
let expectedChecksum =
checksum16Bytes (BL.toStrict (BL.take secretLen rest))
when (checksum /= expectedChecksum) $
fail
("legacy unencrypted secret-key checksum mismatch: expected " ++
show expectedChecksum ++ ", got " ++ show checksum)
return (SKAUnencryptedLegacy sk checksum)
255 ->
case pkp of
PKPayloadV6 {} ->
fail "v6 secret key packets MUST NOT use s2k usage 255"
PKPayloadV3 {} ->
getLegacyS2KProtected SKA16bit
PKPayloadV4 {} ->
getLegacyS2KProtected SKA16bit
254 ->
case pkp of
PKPayloadV6 {} -> do
paramsLen <- getWord8
params <- getLazyByteString (fromIntegral paramsLen)
(symenc, s2k, iv) <-
case runGetOrFail getV6CFBParams params of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing v6 CFB parameters"
| otherwise -> pure parsed
encryptedblock <- getRemainingLazyByteString
return (SKASHA1V6 symenc s2k (IV iv) encryptedblock)
PKPayloadV3 {} ->
getLegacyS2KProtected SKASHA1Legacy
PKPayloadV4 {} ->
getLegacyS2KProtected SKASHA1Legacy
where
getV6CFBParams = do
symencWord <- getWord8
s2kLen <- getWord8
s2kBytes <- getLazyByteString (fromIntegral s2kLen)
s2k <-
case runGetOrFail getS2K s2kBytes of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing bytes in v6 S2K specifier"
| otherwise -> pure parsed
iv <- getRemainingLazyByteString
let symenc = toFVal symencWord
blockSize <- either fail pure (symEncBlockSize symenc)
when (BL.length iv /= fromIntegral blockSize) $
fail "invalid v6 CFB IV length"
pure (symenc, s2k, BL.toStrict iv)
253 ->
case pkp of
PKPayloadV6 {} -> do
paramsLen <- getWord8
params <- getLazyByteString (fromIntegral paramsLen)
(symenc, aead, s2k, iv) <-
case runGetOrFail getV6AEADParams params of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing v6 AEAD parameters"
| otherwise -> pure parsed
encryptedblock <- getRemainingLazyByteString
return (SKAAEADV6 symenc aead s2k (IV iv) encryptedblock)
PKPayloadV3 {} -> do
(symenc, aead, s2k, iv) <- getLegacyAEADParams
encryptedblock <- getRemainingLazyByteString
return (SKAAEADLegacy symenc aead s2k (IV iv) encryptedblock)
PKPayloadV4 {} -> do
(symenc, aead, s2k, iv) <- getLegacyAEADParams
encryptedblock <- getRemainingLazyByteString
return (SKAAEADLegacy symenc aead s2k (IV iv) encryptedblock)
where
getV6AEADParams :: Get (SymmetricAlgorithm, AEADAlgorithm, S2K, B.ByteString)
getV6AEADParams = do
symencWord <- getWord8
aeadWord <- getWord8
s2kLen <- getWord8
s2kBytes <- getLazyByteString (fromIntegral s2kLen)
s2k <-
case runGetOrFail getS2K s2kBytes of
Left (_, _, err) -> fail err
Right (rest, _, parsed)
| not (BL.null rest) -> fail "unexpected trailing bytes in v6 S2K specifier"
| otherwise -> pure parsed
iv <- getRemainingLazyByteString
let symenc = toFVal symencWord
aead = toFVal aeadWord
when (BL.length iv /= fromIntegral (aeadNonceSize aead)) $
fail "invalid v6 AEAD IV length"
pure (symenc, aead, s2k, BL.toStrict iv)
-- v3/v4: no cumulative-params-length octet, no S2K-size octet
getLegacyAEADParams :: Get (SymmetricAlgorithm, AEADAlgorithm, S2K, B.ByteString)
getLegacyAEADParams = do
symencWord <- getWord8
aeadWord <- getWord8
s2k <- getS2K
let aead = toFVal aeadWord
iv <- BL.toStrict <$> getLazyByteString (fromIntegral (aeadNonceSize aead))
pure (toFVal symencWord, aead, s2k, iv)
symenc ->
case pkp of
PKPayloadV6 {} -> do
paramsLen <- getWord8
iv <- getByteString (fromIntegral paramsLen)
let symencAlg = toFVal symenc
blockSize <- either fail pure (symEncBlockSize symencAlg)
when (B.length iv /= blockSize) $
fail "invalid v6 CFB IV length"
encryptedblock <- getRemainingLazyByteString
return (SKASymV6 symencAlg (IV iv) encryptedblock)
PKPayloadV3 {} -> do
blockSize <- either fail pure (symEncBlockSize (toFVal symenc))
iv <- getByteString blockSize
encryptedblock <- getRemainingLazyByteString
return (SKASymLegacy (toFVal symenc) (IV iv) encryptedblock)
PKPayloadV4 {} -> do
blockSize <- either fail pure (symEncBlockSize (toFVal symenc))
iv <- getByteString blockSize
encryptedblock <- getRemainingLazyByteString
return (SKASymLegacy (toFVal symenc) (IV iv) encryptedblock)
putSKAddendum :: SKAddendum -> Either String Put
putSKAddendum (SUS16bit symenc s2k iv encryptedblock) =
Right $ do
putWord8 255
put symenc
put s2k
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendum (SUSSHA1 symenc s2k iv encryptedblock) =
Right $ do
putWord8 254
put symenc
put s2k
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendum (SUSAEAD symenc aead s2k iv encryptedblock) =
Right $ do
putWord8 253
put symenc
putWord8 (fromFVal aead)
put s2k
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendum (SUSym symenc iv encryptedblock) =
Right $ do
put symenc
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendum (SUUnencrypted sk checksum) =
do
putSecret <- putSKey sk
Right $ do
putWord8 0
let skb = runPut putSecret
putLazyByteString skb
putWord16be
(if checksum == 0
then checksum16Bytes (BL.toStrict skb)
else checksum)
checksum16Bytes :: B.ByteString -> Word16
checksum16Bytes =
B.foldl'
(\a b -> fromIntegral ((fromIntegral a + fromIntegral b) `mod` (65536 :: Integer)))
0
putSKAddendumForPKPayload :: SomePKPayload -> SKAddendum -> Put
putSKAddendumForPKPayload pkp ska =
case fromSKAddendumForPKPayload pkp ska of
Left e -> error e
Right (SomeSKAddendumV skaV) ->
putSKAddendumForPKPayloadTyped pkp skaV
putSKAddendumForPKPayloadTyped ::
SomePKPayload
-> SKAddendumV v
-> Put
putSKAddendumForPKPayloadTyped pkp (SKAUnencryptedLegacy sk checksum) = do
putWord8 0
let putSecret =
case putSKeyForPKPayload pkp sk of
Left err -> error err
Right p -> p
skb = runPut putSecret
putLazyByteString skb
putWord16be
(if checksum == 0
then BL.foldl (\a b -> mod (a + fromIntegral b) 0xffff) (0 :: Word16) skb
else checksum)
putSKAddendumForPKPayloadTyped pkp (SKAUnencryptedV6 sk) = do
putWord8 0
let putSecret =
case putSKeyForPKPayload pkp sk of
Left err -> error err
Right p -> p
skb = runPut putSecret
putLazyByteString skb
putSKAddendumForPKPayloadTyped _ (SKASHA1V6 symenc s2k iv encryptedblock) = do
let s2kbs = runPut (put s2k)
paramsLen = 1 + 1 + BL.length s2kbs + fromIntegral (B.length (unIV iv))
putWord8 254
putWord8 (fromIntegral paramsLen)
put symenc
putWord8 (fromIntegral (BL.length s2kbs))
putLazyByteString s2kbs
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendumForPKPayloadTyped _ (SKAAEADV6 symenc aead s2k iv encryptedblock) = do
let s2kbs = runPut (put s2k)
paramsLen = 1 + 1 + 1 + BL.length s2kbs + fromIntegral (B.length (unIV iv))
putWord8 253
putWord8 (fromIntegral paramsLen)
put symenc
putWord8 (fromFVal aead)
putWord8 (fromIntegral (BL.length s2kbs))
putLazyByteString s2kbs
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendumForPKPayloadTyped _ (SKAAEADLegacy symenc aead s2k iv encryptedblock) = do
putWord8 253
put symenc
putWord8 (fromFVal aead)
put s2k
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendumForPKPayloadTyped _ (SKASymV6 symenc iv encryptedblock) = do
putWord8 (fromFVal symenc)
putWord8 (fromIntegral (B.length (unIV iv)))
putByteString (unIV iv)
putLazyByteString encryptedblock
putSKAddendumForPKPayloadTyped _ skaV =
case putSKAddendum (toSKAddendum skaV) of
Left e -> error e
Right p -> p
aeadNonceSize :: AEADAlgorithm -> Int
aeadNonceSize EAX = 16
aeadNonceSize OCB = 15
aeadNonceSize GCM = 12
aeadNonceSize (OtherAEADAlgo _) = 0
symEncBlockSize :: SymmetricAlgorithm -> Either String Int
symEncBlockSize Plaintext = Right 0
symEncBlockSize IDEA = Right 8
symEncBlockSize TripleDES = Right 8
symEncBlockSize CAST5 = Right 8
symEncBlockSize Blowfish = Right 8
symEncBlockSize AES128 = Right 16
symEncBlockSize AES192 = Right 16
symEncBlockSize AES256 = Right 16
symEncBlockSize Twofish = Right 16
symEncBlockSize Camellia128 = Right 16
symEncBlockSize Camellia192 = Right 16
symEncBlockSize Camellia256 = Right 16
symEncBlockSize sa =
Left ("unsupported symmetric algorithm for secret-key IV sizing: " ++ show sa)
decodeIterationCount :: Word8 -> IterationCount
decodeIterationCount c =
IterationCount
((16 + (fromIntegral c .&. 15)) `shiftL` ((fromIntegral c `shiftR` 4) + 6))
encodeIterationCount :: IterationCount -> Word8 -- should this really be a lookup table?
encodeIterationCount 1024 = 0
encodeIterationCount 1088 = 1
encodeIterationCount 1152 = 2
encodeIterationCount 1216 = 3
encodeIterationCount 1280 = 4
encodeIterationCount 1344 = 5
encodeIterationCount 1408 = 6
encodeIterationCount 1472 = 7
encodeIterationCount 1536 = 8
encodeIterationCount 1600 = 9
encodeIterationCount 1664 = 10
encodeIterationCount 1728 = 11
encodeIterationCount 1792 = 12
encodeIterationCount 1856 = 13
encodeIterationCount 1920 = 14
encodeIterationCount 1984 = 15
encodeIterationCount 2048 = 16
encodeIterationCount 2176 = 17
encodeIterationCount 2304 = 18
encodeIterationCount 2432 = 19
encodeIterationCount 2560 = 20
encodeIterationCount 2688 = 21
encodeIterationCount 2816 = 22
encodeIterationCount 2944 = 23
encodeIterationCount 3072 = 24
encodeIterationCount 3200 = 25
encodeIterationCount 3328 = 26
encodeIterationCount 3456 = 27
encodeIterationCount 3584 = 28
encodeIterationCount 3712 = 29
encodeIterationCount 3840 = 30
encodeIterationCount 3968 = 31
encodeIterationCount 4096 = 32
encodeIterationCount 4352 = 33
encodeIterationCount 4608 = 34
encodeIterationCount 4864 = 35
encodeIterationCount 5120 = 36
encodeIterationCount 5376 = 37
encodeIterationCount 5632 = 38
encodeIterationCount 5888 = 39
encodeIterationCount 6144 = 40
encodeIterationCount 6400 = 41
encodeIterationCount 6656 = 42
encodeIterationCount 6912 = 43
encodeIterationCount 7168 = 44
encodeIterationCount 7424 = 45
encodeIterationCount 7680 = 46
encodeIterationCount 7936 = 47
encodeIterationCount 8192 = 48
encodeIterationCount 8704 = 49
encodeIterationCount 9216 = 50
encodeIterationCount 9728 = 51
encodeIterationCount 10240 = 52
encodeIterationCount 10752 = 53
encodeIterationCount 11264 = 54
encodeIterationCount 11776 = 55
encodeIterationCount 12288 = 56
encodeIterationCount 12800 = 57
encodeIterationCount 13312 = 58
encodeIterationCount 13824 = 59
encodeIterationCount 14336 = 60
encodeIterationCount 14848 = 61
encodeIterationCount 15360 = 62
encodeIterationCount 15872 = 63
encodeIterationCount 16384 = 64
encodeIterationCount 17408 = 65
encodeIterationCount 18432 = 66
encodeIterationCount 19456 = 67
encodeIterationCount 20480 = 68
encodeIterationCount 21504 = 69
encodeIterationCount 22528 = 70
encodeIterationCount 23552 = 71
encodeIterationCount 24576 = 72
encodeIterationCount 25600 = 73
encodeIterationCount 26624 = 74
encodeIterationCount 27648 = 75
encodeIterationCount 28672 = 76
encodeIterationCount 29696 = 77
encodeIterationCount 30720 = 78
encodeIterationCount 31744 = 79
encodeIterationCount 32768 = 80
encodeIterationCount 34816 = 81
encodeIterationCount 36864 = 82
encodeIterationCount 38912 = 83
encodeIterationCount 40960 = 84
encodeIterationCount 43008 = 85
encodeIterationCount 45056 = 86
encodeIterationCount 47104 = 87
encodeIterationCount 49152 = 88
encodeIterationCount 51200 = 89
encodeIterationCount 53248 = 90
encodeIterationCount 55296 = 91
encodeIterationCount 57344 = 92
encodeIterationCount 59392 = 93
encodeIterationCount 61440 = 94
encodeIterationCount 63488 = 95
encodeIterationCount 65536 = 96
encodeIterationCount 69632 = 97
encodeIterationCount 73728 = 98
encodeIterationCount 77824 = 99
encodeIterationCount 81920 = 100
encodeIterationCount 86016 = 101
encodeIterationCount 90112 = 102
encodeIterationCount 94208 = 103
encodeIterationCount 98304 = 104
encodeIterationCount 102400 = 105
encodeIterationCount 106496 = 106
encodeIterationCount 110592 = 107
encodeIterationCount 114688 = 108
encodeIterationCount 118784 = 109
encodeIterationCount 122880 = 110
encodeIterationCount 126976 = 111
encodeIterationCount 131072 = 112
encodeIterationCount 139264 = 113
encodeIterationCount 147456 = 114
encodeIterationCount 155648 = 115
encodeIterationCount 163840 = 116
encodeIterationCount 172032 = 117
encodeIterationCount 180224 = 118
encodeIterationCount 188416 = 119
encodeIterationCount 196608 = 120
encodeIterationCount 204800 = 121
encodeIterationCount 212992 = 122
encodeIterationCount 221184 = 123
encodeIterationCount 229376 = 124
encodeIterationCount 237568 = 125
encodeIterationCount 245760 = 126
encodeIterationCount 253952 = 127
encodeIterationCount 262144 = 128
encodeIterationCount 278528 = 129
encodeIterationCount 294912 = 130
encodeIterationCount 311296 = 131
encodeIterationCount 327680 = 132
encodeIterationCount 344064 = 133
encodeIterationCount 360448 = 134
encodeIterationCount 376832 = 135
encodeIterationCount 393216 = 136
encodeIterationCount 409600 = 137
encodeIterationCount 425984 = 138
encodeIterationCount 442368 = 139
encodeIterationCount 458752 = 140
encodeIterationCount 475136 = 141
encodeIterationCount 491520 = 142
encodeIterationCount 507904 = 143
encodeIterationCount 524288 = 144
encodeIterationCount 557056 = 145
encodeIterationCount 589824 = 146
encodeIterationCount 622592 = 147
encodeIterationCount 655360 = 148
encodeIterationCount 688128 = 149
encodeIterationCount 720896 = 150
encodeIterationCount 753664 = 151
encodeIterationCount 786432 = 152
encodeIterationCount 819200 = 153
encodeIterationCount 851968 = 154
encodeIterationCount 884736 = 155
encodeIterationCount 917504 = 156
encodeIterationCount 950272 = 157
encodeIterationCount 983040 = 158
encodeIterationCount 1015808 = 159
encodeIterationCount 1048576 = 160
encodeIterationCount 1114112 = 161
encodeIterationCount 1179648 = 162
encodeIterationCount 1245184 = 163
encodeIterationCount 1310720 = 164
encodeIterationCount 1376256 = 165
encodeIterationCount 1441792 = 166
encodeIterationCount 1507328 = 167
encodeIterationCount 1572864 = 168
encodeIterationCount 1638400 = 169
encodeIterationCount 1703936 = 170
encodeIterationCount 1769472 = 171
encodeIterationCount 1835008 = 172
encodeIterationCount 1900544 = 173
encodeIterationCount 1966080 = 174
encodeIterationCount 2031616 = 175
encodeIterationCount 2097152 = 176
encodeIterationCount 2228224 = 177
encodeIterationCount 2359296 = 178
encodeIterationCount 2490368 = 179
encodeIterationCount 2621440 = 180
encodeIterationCount 2752512 = 181
encodeIterationCount 2883584 = 182
encodeIterationCount 3014656 = 183
encodeIterationCount 3145728 = 184
encodeIterationCount 3276800 = 185
encodeIterationCount 3407872 = 186
encodeIterationCount 3538944 = 187
encodeIterationCount 3670016 = 188
encodeIterationCount 3801088 = 189
encodeIterationCount 3932160 = 190
encodeIterationCount 4063232 = 191
encodeIterationCount 4194304 = 192
encodeIterationCount 4456448 = 193
encodeIterationCount 4718592 = 194
encodeIterationCount 4980736 = 195
encodeIterationCount 5242880 = 196
encodeIterationCount 5505024 = 197
encodeIterationCount 5767168 = 198
encodeIterationCount 6029312 = 199
encodeIterationCount 6291456 = 200
encodeIterationCount 6553600 = 201
encodeIterationCount 6815744 = 202
encodeIterationCount 7077888 = 203
encodeIterationCount 7340032 = 204
encodeIterationCount 7602176 = 205
encodeIterationCount 7864320 = 206
encodeIterationCount 8126464 = 207
encodeIterationCount 8388608 = 208
encodeIterationCount 8912896 = 209
encodeIterationCount 9437184 = 210
encodeIterationCount 9961472 = 211
encodeIterationCount 10485760 = 212
encodeIterationCount 11010048 = 213
encodeIterationCount 11534336 = 214
encodeIterationCount 12058624 = 215
encodeIterationCount 12582912 = 216
encodeIterationCount 13107200 = 217
encodeIterationCount 13631488 = 218
encodeIterationCount 14155776 = 219
encodeIterationCount 14680064 = 220
encodeIterationCount 15204352 = 221
encodeIterationCount 15728640 = 222
encodeIterationCount 16252928 = 223
encodeIterationCount 16777216 = 224
encodeIterationCount 17825792 = 225
encodeIterationCount 18874368 = 226
encodeIterationCount 19922944 = 227
encodeIterationCount 20971520 = 228
encodeIterationCount 22020096 = 229
encodeIterationCount 23068672 = 230
encodeIterationCount 24117248 = 231
encodeIterationCount 25165824 = 232
encodeIterationCount 26214400 = 233
encodeIterationCount 27262976 = 234
encodeIterationCount 28311552 = 235
encodeIterationCount 29360128 = 236
encodeIterationCount 30408704 = 237
encodeIterationCount 31457280 = 238
encodeIterationCount 32505856 = 239
encodeIterationCount 33554432 = 240
encodeIterationCount 35651584 = 241
encodeIterationCount 37748736 = 242
encodeIterationCount 39845888 = 243
encodeIterationCount 41943040 = 244
encodeIterationCount 44040192 = 245
encodeIterationCount 46137344 = 246
encodeIterationCount 48234496 = 247
encodeIterationCount 50331648 = 248
encodeIterationCount 52428800 = 249
encodeIterationCount 54525952 = 250
encodeIterationCount 56623104 = 251
encodeIterationCount 58720256 = 252
encodeIterationCount 60817408 = 253
encodeIterationCount 62914560 = 254
encodeIterationCount 65011712 = 255
encodeIterationCount n = error ("invalid iteration count" ++ show n)
getSignaturePayload :: Get SignaturePayload
getSignaturePayload = do
pv <- getWord8
case pv of
3 -> do
hashlen <- getWord8
guard (hashlen == 5)
st <- getWord8
ctime <- fmap ThirtyTwoBitTimeStamp getWord32be
eok <- getLazyByteString 8
pka <- get
ha <- get
left16 <- getWord16be
mpib <- getRemainingLazyByteString
case runGetOrFail (some getMPI) mpib of
Left (_, _, e) -> fail ("v3 sig MPIs " ++ e)
Right (_, _, mpis) ->
return $
SigV3
(toFVal st)
ctime
(EightOctetKeyId eok)
(toFVal pka)
(toFVal ha)
left16
(NE.fromList mpis)
4 -> do
st <- getWord8
pkaOctet <- get
ha <- get
let pka = toFVal pkaOctet :: PubKeyAlgorithm
hlen <- getWord16be
hb <- getLazyByteString (fromIntegral hlen)
let hashed =
case runGetOrFail (many getSigSubPacket) hb of
Left (_, _, err) -> fail ("v4 sig hasheds " ++ err)
Right (_, _, h) -> h
ulen <- getWord16be
ub <- getLazyByteString (fromIntegral ulen)
let unhashed =
case runGetOrFail (many getSigSubPacket) ub of
Left (_, _, err) -> fail ("v4 sig unhasheds " ++ err)
Right (_, _, u) -> u
left16 <- getWord16be
mpib <- getRemainingLazyByteString
let parseV4MPIs parseErrPrefix =
case runGetOrFail (some getMPI) mpib of
Left (_, _, e) -> fail (parseErrPrefix ++ e)
Right (_, _, mpis) ->
return $
SigV4
(toFVal st)
pka
(toFVal ha)
hashed
unhashed
left16
(NE.fromList mpis)
if pka == BTypes.Ed25519
then
if BL.length mpib == 64
then do
let sig = BL.toStrict mpib
(rbs, sbs) = B.splitAt 32 sig
return $
SigV4
(toFVal st)
pka
(toFVal ha)
hashed
unhashed
left16
(NE.fromList [MPI (os2ip rbs), MPI (os2ip sbs)])
else parseV4MPIs "v4 Ed25519 legacy MPIs "
else
if pka == BTypes.Ed448
then
if BL.length mpib == 114
then do
let sig = BL.toStrict mpib
(rbs, sbs) = B.splitAt 57 sig
return $
SigV4
(toFVal st)
pka
(toFVal ha)
hashed
unhashed
left16
(NE.fromList [MPI (os2ip rbs), MPI (os2ip sbs)])
else parseV4MPIs "v4 Ed448 legacy MPIs "
else parseV4MPIs "v4 sig MPIs "
6 -> do
st <- getWord8
pka <- get
ha <- get
hlen <- getWord32be
hb <- getLazyByteString (fromIntegral hlen)
let hashed =
case runGetOrFail (many getSigSubPacket) hb of
Left (_, _, err) -> fail ("v6 sig hasheds " ++ err)
Right (_, _, h) -> h
ulen <- getWord32be
ub <- getLazyByteString (fromIntegral ulen)
let unhashed =
case runGetOrFail (many getSigSubPacket) ub of
Left (_, _, err) -> fail ("v6 sig unhasheds " ++ err)
Right (_, _, u) -> u
left16 <- getWord16be
saltSize <- getWord8
let haVal = (toFVal ha :: HashAlgorithm)
expectedSaltSize <-
maybe
(fail ("signature hash algorithm does not define a V6 salt size: " ++ show haVal))
pure
(v6SaltSizeForHashAlgorithm haVal)
when (saltSize /= expectedSaltSize) $
fail
("v6 signature salt size mismatch for " ++
show haVal ++ ": expected " ++ show expectedSaltSize ++ ", got " ++ show saltSize)
saltbs <- getByteString (fromIntegral saltSize)
let salt = SignatureSalt (BL.fromStrict saltbs)
if pka == BTypes.Ed25519
then do
sig <- getByteString 64
let (rbs, sbs) = B.splitAt 32 sig
mpis = [MPI (os2ip rbs), MPI (os2ip sbs)]
return $
SigV6
(toFVal st)
pka
(toFVal ha)
salt
hashed
unhashed
left16
(NE.fromList mpis)
else
if pka == BTypes.Ed448
then do
sig <- getByteString 114
let (rbs, sbs) = B.splitAt 57 sig
mpis = [MPI (os2ip rbs), MPI (os2ip sbs)]
return $
SigV6
(toFVal st)
pka
(toFVal ha)
salt
hashed
unhashed
left16
(NE.fromList mpis)
else do
mpib <- getRemainingLazyByteString
case runGetOrFail (some getMPI) mpib of
Left (_, _, e) -> fail ("v6 sig MPIs " ++ e)
Right (_, _, mpis) ->
return $
SigV6
(toFVal st)
pka
(toFVal ha)
salt
hashed
unhashed
left16
(NE.fromList mpis)
_ -> do
bs <- getRemainingLazyByteString
return $ SigVOther pv bs
putSignaturePayload :: SignaturePayload -> Put
putSignaturePayload (SigV3 st ctime eok pka ha left16 mpis) = do
putWord8 3
putWord8 5 -- hashlen
put st
putWord32be . unThirtyTwoBitTimeStamp $ ctime
putLazyByteString (unEOKI eok)
put pka
put ha
putWord16be left16
F.mapM_ put mpis
putSignaturePayload (SigV4 st pka ha hashed unhashed left16 mpis) = do
putWord8 4
put st
put pka
put ha
let hb = runPut $ mapM_ put hashed
putWord16be . fromIntegral . BL.length $ hb
putLazyByteString hb
let ub = runPut $ mapM_ put unhashed
putWord16be . fromIntegral . BL.length $ ub
putLazyByteString ub
putWord16be left16
if pka == BTypes.Ed25519
then
case NE.toList mpis of
[MPI r, MPI s] -> do
putByteString (padN 32 r)
putByteString (padN 32 s)
_ -> error "Ed25519 v4 signatures must have two MPIs"
else
if pka == BTypes.Ed448
then
case NE.toList mpis of
[MPI r, MPI s] -> do
putByteString (padN 57 r)
putByteString (padN 57 s)
_ -> error "Ed448 v4 signatures must have two MPIs"
else F.mapM_ put mpis
where
padN n i =
let bs = i2osp i
in B.replicate (max 0 (n - B.length bs)) 0 <> bs
putSignaturePayload (SigV6 st pka ha salt hashed unhashed left16 mpis) = do
let expectedSaltSize =
maybe
(error ("signature hash algorithm does not define a V6 salt size: " ++ show ha))
id
(v6SaltSizeForHashAlgorithm ha)
actualSaltSize = fromIntegral (BL.length (unSignatureSalt salt))
when (actualSaltSize /= expectedSaltSize) $
error
("v6 signature salt size mismatch for " ++
show ha ++ ": expected " ++ show expectedSaltSize ++ ", got " ++ show actualSaltSize)
putWord8 6
put st
put pka
put ha
let hb = runPut $ mapM_ put hashed
putWord32be . fromIntegral . BL.length $ hb
putLazyByteString hb
let ub = runPut $ mapM_ put unhashed
putWord32be . fromIntegral . BL.length $ ub
putLazyByteString ub
putWord16be left16
putWord8 . fromIntegral . BL.length . unSignatureSalt $ salt
putByteString (BL.toStrict (unSignatureSalt salt))
if pka == BTypes.Ed25519
then
case NE.toList mpis of
[MPI r, MPI s] -> do
putByteString (padN 32 r)
putByteString (padN 32 s)
_ -> error "Ed25519 v6 signatures must have two MPIs"
else
if pka == BTypes.Ed448
then
case NE.toList mpis of
[MPI r, MPI s] -> do
putByteString (padN 57 r)
putByteString (padN 57 s)
_ -> error "Ed448 v6 signatures must have two MPIs"
else F.mapM_ put mpis
where
padN n i =
let bs = i2osp i
in B.replicate (max 0 (n - B.length bs)) 0 <> bs
putSignaturePayload (SigVOther pv bs) = do
putWord8 pv
putLazyByteString bs
putTK :: TKUnknown -> Put
putTK tk = do
let pkp = tk ^. tkuKey . _1
maybe
(put (PublicKey pkp))
(\ska -> put (SecretKey pkp ska))
(snd (tk ^. tkuKey))
mapM_ (put . Signature) (_tkuRevs tk)
mapM_ putUid' (_tkuUIDs tk)
mapM_ putUat' (_tkuUAts tk)
mapM_ putSub' (_tkuSubs tk)
where
putUid' (u, sps) = put (UserId u) >> mapM_ (put . Signature) sps
putUat' (us, sps) = put (UserAttribute us) >> mapM_ (put . Signature) sps
putSub' (p, sps) = put p >> mapM_ (put . Signature) sps
-- | Parse the packets from a ByteString, with no error reporting
parsePkts :: ByteString -> [Pkt]
parsePkts = reverse . fst . parsePktsAccum 0 []
-- | Parse packets from a ByteString and report the first parse failure.
parsePktsEither :: ByteString -> Either PktParseError [Pkt]
parsePktsEither lbs =
case parsePktsAccum 0 [] lbs of
(pkts, Nothing) -> Right (reverse pkts)
(_, Just err) -> Left err
data PktParseError =
PktParseError
{ pktParseErrorOffset :: Int64
, pktParseErrorMessage :: String
}
deriving (Eq, Show)
parsePktsAccum ::
Int64 -> [Pkt] -> ByteString -> ([Pkt], Maybe PktParseError)
parsePktsAccum offset acc lbs
| BL.null lbs = (acc, Nothing)
| otherwise =
case runGetOrFail getPkt lbs of
Left (_, parseOffset, msg) -> (acc, err parseOffset msg)
Right (rest, consumed, pkt) ->
parsePktsAccum (offset + consumed) (pkt : acc) rest
where
err parseOffset msg =
Just
PktParseError
{ pktParseErrorOffset = offset + parseOffset
, pktParseErrorMessage = msg
}
armorPayload :: Armor -> ByteString
armorPayload (Armor _ _ bs) = BL.fromStrict (BLC8.toStrict bs)
armorPayload (ClearSigned _ _ inner) = armorPayload inner
dearmorIfAsciiArmored :: ByteString -> Either String (Bool, ByteString)
dearmorIfAsciiArmored bs
| BLC8.isPrefixOf (BLC8.pack "-----BEGIN PGP ") (BLC8.dropWhile (`elem` (" \t\r\n" :: String)) bs) =
case AA.decodeLazy bs of
Left err -> Left err
Right [] -> Left "ASCII armor decode succeeded but returned no blocks"
Right (a:_) -> Right (True, armorPayload a)
| otherwise = Right (False, bs)
data WireRepInput =
WireRepInput
{ wireRepInputRef :: WireRepRef
, wireRepInputPayload :: ByteString
}
wireRepRefFromInput :: Maybe T.Text -> ByteString -> Either String WireRepInput
wireRepRefFromInput mname bs =
(\(wasArmored, payload) ->
WireRepInput
{ wireRepInputRef = BTypes.mkWireRepRef mname wasArmored payload
, wireRepInputPayload = payload
}) <$>
dearmorIfAsciiArmored bs
data ParseState =
ParseState
{ psOffset :: Int64
, psIndex :: Int
, psSource :: WireRepRef
, psRemaining :: BL.ByteString
}
-- | Parse packets from a source bytestream, preserving packet provenance.
parsePktsWithWireRep :: WireRepRef -> ByteString -> [PktWithWireRep]
parsePktsWithWireRep src input = go initialState
where
initialState =
ParseState
{ psOffset = 0
, psIndex = 0
, psSource = src
, psRemaining = input
}
go state
| BL.null (psRemaining state) = []
| otherwise =
case runGetOrFail getPkt (psRemaining state) of
Left (_, _, _) -> []
Right (rest, consumed, pkt) ->
let raw = BL.take consumed (psRemaining state)
newState =
state
{ psOffset = psOffset state + consumed
, psIndex = psIndex state + 1
, psRemaining = rest
}
pktWithSource =
PktWithWireRep
(psSource state)
(ByteRange (psOffset state) consumed)
raw
(psIndex state)
pkt
in pktWithSource : go newState
conduitParsePktsWithWireRep ::
Monad m => Maybe T.Text -> ConduitT B.ByteString PktWithWireRep m ()
conduitParsePktsWithWireRep mname = go (UndecidedInput [])
where
go !state = do
mchunk <- await
case mchunk of
Nothing -> mapM_ yield (finishConduitState mname state)
Just chunk ->
let !nextState = consumeConduitChunk chunk state
in go nextState
data ConduitParseState
= UndecidedInput ![B.ByteString]
| ArmoredInput ![B.ByteString]
| BinaryInput !BinaryParseState
data BinaryParseState =
BinaryParseState
{ bpsLength :: !Int64
, bpsOffset :: !Int64
, bpsIndex :: !Int
, bpsBuffer :: !B.ByteString
, bpsParsedRev :: [ParsedPacketChunk]
}
data ArmorPrefixDecision
= PrefixNeedsMore
| PrefixIsArmored
| PrefixIsBinary
consumeConduitChunk :: B.ByteString -> ConduitParseState -> ConduitParseState
consumeConduitChunk chunk (UndecidedInput chunksRev) =
let prefixChunksRev = chunk : chunksRev
prefix = B.concat (reverse prefixChunksRev)
in case classifyArmorPrefix prefix of
PrefixNeedsMore -> UndecidedInput prefixChunksRev
PrefixIsArmored -> ArmoredInput prefixChunksRev
PrefixIsBinary -> feedBinaryChunk prefix initialBinaryParseState
consumeConduitChunk chunk (ArmoredInput chunksRev) = ArmoredInput (chunk : chunksRev)
consumeConduitChunk chunk (BinaryInput state) = BinaryInput (advanceBinaryParseState chunk state)
finishConduitState :: Maybe T.Text -> ConduitParseState -> [PktWithWireRep]
finishConduitState mname (UndecidedInput chunksRev) =
finalizeBinaryParseState
mname
(advanceBinaryParseState (B.concat (reverse chunksRev)) initialBinaryParseState)
finishConduitState mname (ArmoredInput chunksRev) =
let input = BL.fromChunks (reverse chunksRev)
WireRepInput
{ wireRepInputRef = src
, wireRepInputPayload = payload
} =
either
(const
WireRepInput
{ wireRepInputRef = BTypes.mkWireRepRef mname False input
, wireRepInputPayload = input
})
id
(wireRepRefFromInput mname input)
in parsePktsWithWireRep src payload
finishConduitState mname (BinaryInput state) = finalizeBinaryParseState mname state
initialBinaryParseState :: BinaryParseState
initialBinaryParseState =
BinaryParseState
{ bpsLength = 0
, bpsOffset = 0
, bpsIndex = 0
, bpsBuffer = B.empty
, bpsParsedRev = []
}
feedBinaryChunk :: B.ByteString -> BinaryParseState -> ConduitParseState
feedBinaryChunk chunk = BinaryInput . advanceBinaryParseState chunk
advanceBinaryParseState :: B.ByteString -> BinaryParseState -> BinaryParseState
advanceBinaryParseState chunk state =
let !nextLength = bpsLength state + fromIntegral (B.length chunk)
!(nextOffset, nextIndex, nextBuffer, nextParsedRev) =
drainParsedPackets
(bpsOffset state)
(bpsIndex state)
(bpsBuffer state <> chunk)
(bpsParsedRev state)
in BinaryParseState
{ bpsLength = nextLength
, bpsOffset = nextOffset
, bpsIndex = nextIndex
, bpsBuffer = nextBuffer
, bpsParsedRev = nextParsedRev
}
finalizeBinaryParseState :: Maybe T.Text -> BinaryParseState -> [PktWithWireRep]
finalizeBinaryParseState mname state =
let src = BTypes.mkWireRepRefWithLength mname False (bpsLength state)
in map (toPktWithWireRep src) (reverse (bpsParsedRev state))
classifyArmorPrefix :: B.ByteString -> ArmorPrefixDecision
classifyArmorPrefix prefix =
case B.dropWhile isLeadingArmorWhitespace prefix of
rest
| B.null rest -> PrefixNeedsMore
| armorHeader `B.isPrefixOf` rest -> PrefixIsArmored
| rest `B.isPrefixOf` armorHeader -> PrefixNeedsMore
| otherwise -> PrefixIsBinary
where
armorHeader = BLC8.toStrict (BLC8.pack "-----BEGIN PGP ")
isLeadingArmorWhitespace w = w `elem` map (fromIntegral . fromEnum) (" \t\r\n" :: String)
data ParsedPacketChunk =
ParsedPacketChunk
{ ppcRange :: ByteRange
, ppcRaw :: ByteString
, ppcIndex :: Int
, ppcValue :: Pkt
}
toPktWithWireRep :: WireRepRef -> ParsedPacketChunk -> PktWithWireRep
toPktWithWireRep src ppc =
PktWithWireRep src (ppcRange ppc) (ppcRaw ppc) (ppcIndex ppc) (ppcValue ppc)
drainParsedPackets ::
Int64
-> Int
-> B.ByteString
-> [ParsedPacketChunk]
-> (Int64, Int, B.ByteString, [ParsedPacketChunk])
drainParsedPackets !offset !idx !buffer acc
| B.null buffer = (offset, idx, B.empty, acc)
| otherwise =
case runGetOrFail getPkt (BL.fromStrict buffer) of
Left _ -> (offset, idx, buffer, acc)
Right (rest, consumed, pkt) ->
let !consumedLen = fromIntegral consumed
!nextOffset = offset + consumed
!nextIdx = idx + 1
!nextBuffer = BL.toStrict rest
parsedPacket =
ParsedPacketChunk
{ ppcRange = ByteRange offset consumed
, ppcRaw = BL.fromStrict (B.take consumedLen buffer)
, ppcIndex = idx
, ppcValue = pkt
}
in drainParsedPackets
nextOffset
nextIdx
nextBuffer
(parsedPacket : acc)