domain-auth-0.2.5: Network/DomainAuth/DKIM/Verify.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.DomainAuth.DKIM.Verify (
verifyDKIM,
prepareDKIM,
) where
import Crypto.Hash
import Crypto.PubKey.RSA
import Crypto.PubKey.RSA.PKCS15
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.ByteString.Builder (Builder)
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Network.DomainAuth.DKIM.Btag
import Network.DomainAuth.DKIM.Parser
import Network.DomainAuth.DKIM.Types
import Network.DomainAuth.Mail
import qualified Network.DomainAuth.Pubkey.Base64 as B
import Network.DomainAuth.Utils
----------------------------------------------------------------
-- | Fields are selected from the entire header except the signature
-- being verified, bottom-up (RFC 6376 Section 5.4.2).
prepareDKIM :: DKIM -> Mail -> Maybe Builder
prepareDKIM dkim mail = case break isTheSig (mailHeader mail) of
(_, []) -> Nothing
(pre, dkimField : post) ->
let targets = fieldsWith (dkimFields dkim) (pre ++ post)
in Just $ concatCRLFWith hCanon targets +++ canon dkimField
where
isTheSig fld =
fieldSearchKey fld == dkimFieldKey
&& parseDKIM (fieldValueUnfolded fld) == Just dkim
hCanon = canonDkimField (dkimHeaderCanon dkim)
canon = BB.byteString . removeBtagValue . hCanon
----------------------------------------------------------------
canonDkimField :: DkimCanonAlgo -> Field -> ByteString
canonDkimField DKIM_SIMPLE fld = fieldKey fld +++ ": " +++ fieldValueFolded fld
canonDkimField DKIM_RELAXED fld = fieldSearchKey fld +++ ":" +++ canon fld
where
canon = BS.dropWhile isSpace . removeTrailingWSP . reduceWSP . BS.concat . fieldValue
----------------------------------------------------------------
canonDkimBody :: DkimCanonAlgo -> Body -> Builder
canonDkimBody DKIM_SIMPLE = fromBody . removeTrailingEmptyLine
canonDkimBody DKIM_RELAXED = fromBodyWith relax . removeTrailingEmptyLine
where
relax = removeTrailingWSP . reduceWSP
----------------------------------------------------------------
verifyDKIM :: Mail -> DKIM -> PublicKey -> Bool
verifyDKIM mail dkim pub = case (mbh, msig, mhdr) of
(Just bh, Just sig, Just hdr) ->
matchHash alg bh body && verify' alg pub (toBS hdr) sig
_ -> False
where
alg = dkimSigAlgo dkim
mbh = B.decode $ dkimBodyHash dkim
msig = B.decode $ dkimSignature dkim
mhdr = prepareDKIM dkim mail
toBS = BL.toStrict . BB.toLazyByteString
body =
BL.toStrict
. BB.toLazyByteString
. canonDkimBody (dkimBodyCanon dkim)
$ mailBody mail
verify' :: DkimSigAlgo -> PublicKey -> ByteString -> ByteString -> Bool
verify' RSA_SHA1 = verify (Just SHA1)
verify' RSA_SHA256 = verify (Just SHA256)
-- | Checking if the body hash matches the decoded "bh" value.
matchHash :: DkimSigAlgo -> ByteString -> ByteString -> Bool
matchHash RSA_SHA1 bh txt = digestFromByteString bh == Just (hashWith SHA1 txt)
matchHash RSA_SHA256 bh txt = digestFromByteString bh == Just (hashWith SHA256 txt)