cryptopals

Matasano's cryptopals challenges (cryptopals.com).
Log | Files | Refs | README | LICENSE

commit ad29fe190d65b857fcd4411d6f2b5ed583ea7081
parent 8bd46b8164ab79a1152a9e22753c3f183acaadee
Author: Jared Tobin <jared@jtobin.io>
Date:   Tue, 15 Aug 2023 18:28:49 -0230

Add 5.35.

Diffstat:
Mdocs/s5.md | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mlib/Cryptopals/DH.hs | 72++++++++++++++++++++++++++++++++++++++----------------------------------
Mlib/Cryptopals/DH/Session.hs | 416+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
3 files changed, 417 insertions(+), 233 deletions(-)

diff --git a/docs/s5.md b/docs/s5.md @@ -62,13 +62,24 @@ That's all well and good, but let's have a bit of fun. Cryptopals.DH implements the Diffie-Hellman protocol over TCP. Two functions, 'bob' and 'alice', will initiate a TCP server and client, -respectively. Each end will then cooperate to perform Diffie-Hellman, -for the lulz then exchanging an AES128-encrypted message with the shared -key à la the initial illustration in the next challenge. +respectively. Each takes as argument a port to bind to and a protocol to +follow, with Alice also taking an argument specifying the initial action +she'll take. The two will then perform the specified protocol. -Opening two instances of GHCi, we can run 'bob' in one and 'alice' in -the other and watch the logs for fun. Here I'll interleave the relevant -parts of the logs for readability: +The 'dh' protocol specifies Diffie-Hellman, and for the lulz Alice and +Bob will exchange a message, AES128-encrypted with the shared key, à la +the initial illustration in the next challenge. + +Opening two instances of GHCi, we can run e.g.: + + > bob "3000" dh + +in one, and: + + > alice "3000" dh + +in the other and watch the logs for fun. Here I'll interleave the +relevant parts of the logs for readability: (cryptopals) bob: listening.. (cryptopals) alice: session established @@ -76,26 +87,38 @@ parts of the logs for readability: (cryptopals) bob: received group parameters and public key (cryptopals) bob: sending public key (cryptopals) alice: received public key - (cryptopals) alice: sending ciphertext - (cryptopals) bob: received ciphertext + (cryptopals) alice: sending ciphertext fQwcd2smXxBojyEDLrIdySNLAV3UHhl8/X2F/x4FIb0= + (cryptopals) bob: received ciphertext fQwcd2smXxBojyEDLrIdySNLAV3UHhl8/X2F/x4FIb0= (cryptopals) bob: decrypted ciphertext: "attack at 10pm" - (cryptopals) bob: replying with ciphertext - (cryptopals) alice: received ciphertext + (cryptopals) bob: replying with ciphertext NaObgnz4rNShMKRGdGv+OGnI2gZnWOoXuYmCZhlcyymRbHPaprFVOz4Ls5eH9y/W + (cryptopals) alice: received ciphertext NaObgnz4rNShMKRGdGv+OGnI2gZnWOoXuYmCZhlcyymRbHPaprFVOz4Ls5eH9y/W (cryptopals) alice: decrypted ciphertext: "confirmed, attacking at 10pm" (cryptopals) bob: ending session #### 5.34 If B = p in s = B ^ a mod p, then s = p ^ a mod p, which is zero for any -'a' in the group. Our key is thus going to be the first 16 bytes of the -SHA1 hash of an appropriately-serialized 0x00. +'a' in the group. Our shared key is thus going to be the first 16 bytes +of the SHA1 hash of an appropriately-serialized 0x00. + +Cryptopals.DH includes a 'mallory' agent that requires a port to listen +on, a port to bind to, and a protocol to follow. By using the 'dhmitm' +protocol, we get our man-in-the-middle attack on Alice and Bob's DH key +exchange. + +You can get this going by opening three GHCi's, then launching e.g.: + + > bob "3000" dh + +in one, then: + + > mallory "3001" "3000" dhmitm + +in another, and finally: + + > alice "3001" dh sendParams -Thus, Mallory will learn the key and trivially be able to decrypt -messages. Adding a 'mallory' agent to Cryptopals.DH, we get our MITM -attack on the above DH key exchange. You can get this going by opening -three GHCi's, then launching e.g. `bob "3000"` in one, `mallory "3001" -"3000"` in another, and then `alice "3001"` in the last. Again, I'm -interleaving the logs for readability: +in the third. Again, I'm interleaving the logs for readability: (cryptopals) bob: listening.. (cryptopals) mallory: LiSteNIng.. @@ -103,7 +126,7 @@ interleaving the logs for readability: (cryptopals) mallory: eStabLisHed coNNecTion (cryptopals) alice: sending group parameters and public key (cryptopals) mallory: reCEiVed GRoUp pArAmeTErs And pUBliC kEy - (cryptopals) mallory: sEnDinG BOguS paRaMs + (cryptopals) mallory: sEnDinG BOguS paRaMeTeRs (cryptopals) bob: received group parameters and public key (cryptopals) bob: sending public key (cryptopals) mallory: REceIvED pUBlic keY @@ -122,3 +145,104 @@ interleaving the logs for readability: (cryptopals) alice: received ciphertext ux4PoPTCS7pz5H4IQ11AuZkMBHmEcT9Waz68y/a9nggIY38Z6mbwSrCwNO3OKcDQ (cryptopals) alice: decrypted ciphertext: "confirmed, attacking at 10pm" +#### 5.35 + +Cryptopals.DH.dhng implements the negotiated-groups DH protocol, so that +can be run by firing off e.g.: + + > bob "3000" dhng + +in one GHCi session, and: + + > alice "3000" dhng sendGroup + +in the other. In the meantime, we can figure out the outcomes of using +the different malicious group parameters analytically. + +For g = 1, the MITM attack starts as follows: + + alice sends p, g + bob gets p, 1 + +If Bob receives g = 1, then Mallory knows his public key will equal 1 ^ +b mod p = 1, as will the shared key from Alice's perspective (since 1 ^ +a mod p = 1). Mallory thus needs to forward a 1 as Alice's public key in +order for Bob to agree on the shared key. + +For g = p, Bob computes B = p ^ b mod p = 0, so Mallory needs to forward +a 0 as Alice's public key in order for them to agree on the shared key. + +Finally, the case of g = p - 1. Note that for any p > 1 and any even b, we +have (for appropriate coefficients a, c, etc.): + + (p - 1) ^ b mod p + = (p^b + .. + ap^2 + cp + 1) mod p + = (p^b mod p + .. + ap^2 mod p + cp mod p + 1 mod p) mod p + = (0 + .. + 0 + 1 mod p) mod p + = 1 + +whereas for any odd b, we have: + + (p - 1) ^ b mod p + = (p^b - .. - cp^2 + dp - 1) mod p + = (p^b mod p - .. - ap^2 mod p + cp mod p - 1 mod p) mod p + = (0 + .. + 0 - 1 mod p) mod p + = p - 1. + +So Bob's public key will be either 1 or p - 1 depending on whether his +secret key is even or odd. Alice will thus compute: + + s = B ^ a mod p + = 1 } b even or a even + p - 1 } b odd and a odd. + +If Mallory thus forwards A = 1 to Bob, we have: + + t = A ^ b mod p + = 1. + +So, Alice and Bob will agree on the key 1 (i.e., the attack succeeds) +whenever 'b' is even or 'a' is even, an event that occurs with +probability 1/2 + 1/2 - 1/4 = 3/4. + +(Mallory could ensure the attack works every time by forwarding 1's for +*both* public keys, but that seems against the spirit of the question.) + +Here are the interleaved logs of a successful attack: + + (cryptopals) bob: listening.. + (cryptopals) mallory: LiSteNIng.. + (cryptopals) alice: session established + (cryptopals) mallory: eStabLisHed MiTm coNNecTion + (cryptopals) alice: sending group parameters + (cryptopals) mallory: reCEiVed GRoUp pArAmeTErs + (cryptopals) mallory: sEnDinG BOguS GRoUp paRaMeTeRs + (cryptopals) bob: received group parameters + (cryptopals) bob: acking group parameters + (cryptopals) mallory: rECeiVed aCK + (cryptopals) mallory: ReLaYINg ACk + (cryptopals) alice: received ack + (cryptopals) alice: sending public key 3f44f49421cbb3b2ed40aa8f068236affba15335 + (cryptopals) mallory: REceIvED pUBlic keY 3f44f49421cbb3b2ed40aa8f068236affba15335 + (cryptopals) mallory: SeNDing BoGuS kEy d14952314d5de233ef0dd0a178617f7f07ea082c + (cryptopals) bob: received public key d14952314d5de233ef0dd0a178617f7f07ea082c + (cryptopals) bob: sending public key + (cryptopals) mallory: REceIvED pUBlic keY d14952314d5de233ef0dd0a178617f7f07ea082c + (cryptopals) mallory: ReLAyINg pUbliC KeY d14952314d5de233ef0dd0a178617f7f07ea082c + (cryptopals) alice: received public key d14952314d5de233ef0dd0a178617f7f07ea082c + (cryptopals) alice: sending ciphertext +nbU0t3nLX3WmKoY3+pdmilVcd2I6fJfGuC3RTn0h5E= + (cryptopals) mallory: rECeIveD CiPHeRTexT +nbU0t3nLX3WmKoY3+pdmilVcd2I6fJfGuC3RTn0h5E= + (cryptopals) mallory: DEcRyptEd cIPheRTeXt: "attack at 10pm" + (cryptopals) mallory: reLayINg cIpheRtExt + (cryptopals) bob: received ciphertext +nbU0t3nLX3WmKoY3+pdmilVcd2I6fJfGuC3RTn0h5E= + (cryptopals) bob: decrypted ciphertext: "attack at 10pm" + (cryptopals) bob: replying with ciphertext 3i7fLAZXJv7+cr3qrI8KDKhfe6FpJq62yVtaCt9dlrUodMiRVtJ7ZmKtJ8ku0r4x + (cryptopals) mallory: reCeiVeD CipHeRtExt 3i7fLAZXJv7+cr3qrI8KDKhfe6FpJq62yVtaCt9dlrUodMiRVtJ7ZmKtJ8ku0r4x + (cryptopals) mallory: DeCrYpteD cIphErteXt: "confirmed, attacking at 10pm" + (cryptopals) mallory: ReLaYINg CiPHeRTexT + (cryptopals) alice: received ciphertext 3i7fLAZXJv7+cr3qrI8KDKhfe6FpJq62yVtaCt9dlrUodMiRVtJ7ZmKtJ8ku0r4x + (cryptopals) alice: decrypted ciphertext: "confirmed, attacking at 10pm" + (cryptopals) mallory: ending session + + + diff --git a/lib/Cryptopals/DH.hs b/lib/Cryptopals/DH.hs @@ -25,17 +25,11 @@ import qualified System.Random.MWC as MWC bob :: (DB.Binary b, DB.Binary c) => PN.ServiceName - -> Handler (StateT Sesh IO) b c + -> Protocol (StateT Sesh IO) b c -> IO () bob port eval = PN.serve "localhost" port $ \(sock, _) -> do let host = "bob" - sesh = Sesh { - dhGroup = Nothing - , dhHost = host - , dhKeys = Nothing - , dhKey = Nothing - , dhGen = MWC.createSystemRandom - } + sesh = open sock host blog host "listening.." void $ S.evalStateT (runEffect (session sock eval)) sesh @@ -43,48 +37,58 @@ bob port eval = PN.serve "localhost" port $ \(sock, _) -> do alice :: (DB.Binary b, DB.Binary c) => PN.ServiceName - -> Handler (StateT Sesh IO) b c + -> Protocol (StateT Sesh IO) b c + -> StateT Sesh IO Command -> IO () -alice port eval = PN.connect "localhost" port $ \(sock, _) -> do +alice port eval knit = PN.connect "localhost" port $ \(sock, _) -> do let host = "alice" + sesh = open sock host blog host "session established" - let grp = Group p g - gen <- MWC.createSystemRandom - per@Keys {..} <- genpair grp gen - blog host "sending group parameters and public key" - runEffect $ do - PB.encode (Just (SendParams grp pub)) + (cmd, nex) <- S.runStateT knit sesh + + runEffect $ + PB.encode (Just cmd) >-> PN.toSocket sock - let sesh = Sesh { - dhGroup = Just grp - , dhHost = host - , dhKeys = Just per - , dhKey = Nothing - , dhGen = pure gen - } - void $ S.runStateT (runEffect (session sock eval)) sesh + void $ S.runStateT (runEffect (session sock eval)) nex --- await key exchange, initiate key exchange +-- await key exchange mallory :: (DB.Binary b, DB.Binary c) => PN.ServiceName -> PN.ServiceName - -> Handler (StateT Sesh IO) b c + -> Protocol (StateT Sesh IO) b c -> IO () mallory port bport eval = do let host = "mallory" PN.serve "localhost" port $ \(asock, _) -> do + let sesh = open asock host blog host "LiSteNIng.." PN.connect "localhost" bport $ \(bsock, _) -> do - let sesh = Sesh { - dhGroup = Nothing - , dhHost = host - , dhKeys = Nothing - , dhKey = Nothing - , dhGen = MWC.createSystemRandom - } - blog host "eStabLisHed coNNecTion" + blog host "eStabLisHed MiTm coNNecTion" void $ S.runStateT (runEffect (dance asock bsock eval)) sesh +-- initialize session with basic stuff +open :: PN.Socket -> T.Text -> Sesh +open sock host = Sesh { + dhGroup = Nothing + , dhHost = host + , dhSock = sock + , dhKeys = Nothing + , dhKey = Nothing + , dhGen = MWC.createSystemRandom + } + +sendParams :: StateT Sesh IO Command +sendParams = do + grp <- genGroup p g + Keys {..} <- genKeypair + slog "sending group parameters and public key" + pure (SendParams grp pub) + +sendGroup :: StateT Sesh IO Command +sendGroup = do + grp <- genGroup p g + slog "sending group parameters" + pure (SendGroup grp) diff --git a/lib/Cryptopals/DH/Session.hs b/lib/Cryptopals/DH/Session.hs @@ -3,13 +3,22 @@ module Cryptopals.DH.Session ( Command(..) + , genGroup + , genKeypair + + , Sesh(..) - , Handler + , Protocol , blog , slog - , beval - , meval + + , dh + , dhng + + , dhmitm + , dhngmitm + , dhngmitm' , session , dance @@ -22,9 +31,12 @@ import Control.Monad.Trans.State (StateT) import qualified Control.Monad.Trans.State as S import qualified Cryptopals.AES as AES import Cryptopals.DH.Core +import qualified Cryptopals.Digest.Pure.SHA as CS import qualified Cryptopals.Util as CU import qualified Data.Binary as DB import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL +import qualified Data.ByteString.Base16 as B16 import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as B8 import qualified Data.Char as C @@ -54,13 +66,13 @@ data Command = instance DB.Binary Command -type Handler m b c = b -> m c +type Protocol m b c = b -> m c -- session state data Sesh = Sesh { dhGroup :: Maybe Group , dhHost :: T.Text - -- , dhSock :: PN.Socket -- XX add me + , dhSock :: PN.Socket , dhKeys :: Maybe Keys , dhKey :: Maybe BS.ByteString , dhGen :: IO (MWC.Gen RealWorld) @@ -75,12 +87,17 @@ slog :: T.Text -> StateT Sesh IO () slog msg = do host <- S.gets dhHost liftIO $ TIO.putStrLn $ "(cryptopals) " <> host <> ": " <> msg + liftIO suspense + +-- dramatic effect +suspense :: IO () +suspense = threadDelay 1000000 -- basic TCP coordination session :: (MonadIO m, DB.Binary b, DB.Binary c) => PN.Socket - -> Handler m b c + -> Protocol m b c -> Effect m (PB.DecodingError, Producer BS.ByteString m ()) session sock eval = deco @@ -97,7 +114,7 @@ dance :: (MonadIO m, DB.Binary b, DB.Binary c) => PN.Socket -> PN.Socket - -> Handler m b c + -> Protocol m b c -> Effect m (PB.DecodingError, Producer BS.ByteString m ()) dance asock bsock eval = PP.parsed PB.decode recv @@ -117,22 +134,28 @@ seval cont = \case slog "ending session" liftIO $ SE.exitSuccess -- XX should really just close the socket Just cmd -> do - liftIO $ threadDelay 3000000 + liftIO suspense cont cmd -- basic dh evaluation -beval :: Maybe Command -> StateT Sesh IO (Maybe Command) -beval = seval dheval +dh :: Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) +dh = seval dheval -- mitm dh evaluation -meval :: Maybe Command -> StateT Sesh IO (Maybe Command) -meval = seval mitmeval +dhmitm :: Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) +dhmitm = seval mitmeval -- negotiated-group dh evaluation -geval :: Maybe Command -> StateT Sesh IO (Maybe Command) -geval = seval ngeval +dhng :: Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) +dhng = seval ngeval + +-- mitm negotiated-group dh evaluation +dhngmitm :: Natural -> Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) +dhngmitm = seval . malgeval --- XX refactor some common actions, e.g. assembling ciphertexts +-- mitm negotiated-group dh evaluation, g = p - 1 +dhngmitm' :: Protocol (StateT Sesh IO) (Maybe Command) (Maybe Command) +dhngmitm' = seval malgeval' -- diffie-hellman protocol eval dheval @@ -148,81 +171,37 @@ dheval = \case pure Nothing SendParams grp pk -> do - sesh@Sesh {..} <- S.get - slog "received group parameters and public key" - gen <- liftIO dhGen - per@Keys {..} <- liftIO $ genpair grp gen - let key = derivekey grp per pk - nex = sesh { - dhGroup = Just grp - , dhKeys = Just per - , dhKey = Just key - } - S.put nex - slog "sending public key" + slog $ "received group parameters and public key " <> renderkey pk + S.modify (\sesh -> sesh { dhGroup = Just grp }) + Keys {..} <- genKeypair + deriveKey pk + slog $ "sending public key " <> renderkey pk pure $ Just (SendPublic pub) SendPublic pk -> do + slog $ "received public key " <> renderkey pk sesh@Sesh {..} <- S.get - slog "received public key" - let key = do - per@Keys {..} <- dhKeys - grp <- dhGroup - pure $ derivekey grp per pk - case key of - Nothing -> do - slog "key derivation failed" - pure Nothing - Just k -> do - gen <- liftIO dhGen - iv <- liftIO $ CU.bytes 16 gen - let msg = CU.lpkcs7 "attack at 10pm" - cip = AES.encryptCbcAES128 iv k msg - cod = B64.encodeBase64 cip - slog $ "sending ciphertext " <> cod - let rep = Just (SendMessage cip) - nex = sesh { dhKey = key } - S.put nex - pure rep + k <- deriveKey pk + cip <- encrypt "attack at 10pm" + S.put sesh { dhKey = Just k } + slog $ "sending ciphertext " <> B64.encodeBase64 cip + pure $ Just (SendMessage cip) SendMessage cip -> do + slog $ "received ciphertext " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "received ciphertext " <> cod - case dhKey of - Nothing -> do - slog "shared key not established" - pure Nothing - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "decrypted ciphertext: \"" <> cod <> "\"" - - let hourOfDestiny = case B8.findIndex C.isDigit msg of - Nothing -> error "did i fat-finger a digit?" - Just j -> BS.drop j msg - - gen <- liftIO dhGen - iv <- liftIO $ CU.bytes 16 gen - let nmsg = CU.lpkcs7 $ "confirmed, attacking at " <> hourOfDestiny - ncip = AES.encryptCbcAES128 iv k nmsg - ncod = B64.encodeBase64 ncip - slog $ "replying with ciphertext " <> ncod - pure $ Just (SendTerminal ncip) + msg <- decrypt cip + slog $ "decrypted ciphertext: \"" <> TE.decodeLatin1 msg <> "\"" + ncip <- encrypt $ "confirmed, attacking at 10pm" + slog $ "replying with ciphertext " <> B64.encodeBase64 ncip + pure $ Just (SendTerminal ncip) SendTerminal cip -> do + slog $ "received ciphertext " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "received ciphertext " <> cod - case dhKey of - Nothing -> do - slog "shared key not established" - pure Nothing - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "decrypted ciphertext: \"" <> cod <> "\"" - pure Nothing + msg <- decrypt cip + slog $ "decrypted ciphertext: \"" <> TE.decodeLatin1 msg <> "\"" + pure Nothing -- man-in-the-middle protocol eval mitmeval @@ -230,44 +209,38 @@ mitmeval -> StateT Sesh IO (Maybe Command) mitmeval = \case SendParams grp pk -> do + slog $ "reCEiVed GRoUp pArAmeTErs And pUBliC kEy " <> renderkey pk sesh@Sesh {..} <- S.get - slog "reCEiVed GRoUp pArAmeTErs And pUBliC kEy" let key = derivekey grp (Keys p 1) p nex = sesh { dhKey = Just key } S.put nex - slog "sEnDinG BOguS paRaMeTeRs" + slog $ "sEnDinG BOguS paRaMeTeRs wIth PuBLiC kEy " <> renderkey p pure $ Just (SendParams grp p) SendPublic pk -> do - slog "REceIvED pUBlic keY" - slog "seNDINg boGus kEy" + slog $ "REceIvED pUBlic keY " <> renderkey pk + slog $ "seNDINg boGus kEy " <> renderkey p pure $ Just (SendPublic p) SendMessage cip -> do + slog $ "rECeIveD CiPHeRTexT " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "rECeIveD CiPHeRTexT " <> cod - case dhKey of - Nothing -> error "mallory knows key" - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "DEcRyptEd cIPheRTeXt: \"" <> cod <> "\"" - slog "reLayINg cIpheRtExt" - pure $ Just (SendMessage cip) + msg <- decrypt cip + slog $ "DEcRyptEd cIPheRTeXt: \"" <> TE.decodeLatin1 msg <> "\"" + slog "reLayINg cIpheRtExt" + pure $ Just (SendMessage cip) SendTerminal cip -> do + slog $ "reCeiVeD CipHeRtExt " <> B64.encodeBase64 cip sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "reCeiVeD CipHeRtExt " <> cod - case dhKey of - Nothing -> error "mallory knows key" - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "DeCrYpteD cIphErteXt: \"" <> cod <> "\"" - slog "ReLaYINg CiPHeRTexT" - pure $ Just (SendTerminal cip) + msg <- decrypt cip + slog $ "DeCrYpteD cIphErteXt: \"" <> TE.decodeLatin1 msg <> "\"" + slog "ReLaYINg CiPHeRTexT" + pure $ Just (SendTerminal cip) + + cmd -> do + slog "RelAyInG coMmaNd" + pure (Just cmd) -- negotiated-group protocol eval ngeval @@ -275,95 +248,178 @@ ngeval -> StateT Sesh IO (Maybe Command) ngeval = \case SendGroup grp -> do - sesh@Sesh {..} <- S.get slog "received group parameters" - let nex = sesh { dhGroup = Just grp } - S.put nex - slog "ACK" + sesh@Sesh {..} <- S.get + S.put sesh { dhGroup = Just grp } + slog "acking group parameters" pure (Just AckGroup) AckGroup -> do + slog "received ack" sesh@Sesh {..} <- S.get - slog "ACK ACK" - gen <- liftIO dhGen - case dhGroup of - Nothing -> do - slog "haven't generated group yet" - pure Nothing - Just grp -> do - per@Keys {..} <- liftIO $ genpair grp gen - let nex = sesh { dhKeys = Just per } - S.put nex - slog "sending public key" - pure $ Just (SendPublic pub) + Keys {..} <- genKeypair + slog $ "sending public key " <> renderkey pub + pure $ Just (SendPublic pub) - SendParams grp pk -> do + SendParams {} -> do slog "not expecting group parameters and public key" pure Nothing SendPublic pk -> do + slog $ "received public key " <> renderkey pk sesh@Sesh {..} <- S.get - slog "received public key" - case dhGroup of + case dhKeys of Nothing -> do - slog "don't have group parameters" - pure Nothing - Just grp -> case dhKeys of - Nothing -> do - gen <- liftIO dhGen - per@Keys {..} <- liftIO $ genpair grp gen - let nex = sesh { dhKeys = Just per } - S.put nex - slog "sending public key" - pure (Just (SendPublic pub)) - Just per@Keys {..} -> do - let key = derivekey grp per pk - nex = sesh { dhKey = Just key } - S.put nex - gen <- liftIO dhGen - iv <- liftIO $ CU.bytes 16 gen - let msg = CU.lpkcs7 "attack at 10pm" - cip = AES.encryptCbcAES128 iv key msg - cod = B64.encodeBase64 cip - slog $ "sending ciphertext " <> cod - pure $ Just (SendMessage cip) + Keys {..} <- genKeypair + key <- deriveKey pk + slog "sending public key" + pure (Just (SendPublic pub)) + Just Keys {..} -> do + key <- deriveKey pk + cip <- encrypt "attack at 10pm" + slog $ "sending ciphertext " <> B64.encodeBase64 cip + pure (Just (SendMessage cip)) + + cmd -> dheval cmd + +-- negotiated-group mitm protocol eval +malgeval + :: Natural + -> Command + -> StateT Sesh IO (Maybe Command) +malgeval malg = \case + SendGroup grp -> do + slog "reCEiVed GRoUp pArAmeTErs" + sesh <- S.get + let key = derivekey grp (Keys p malg) malg + S.put sesh { + dhGroup = Just grp + , dhKey = Just key + } + let malgrp = Group p malg + slog "sEnDinG BOguS GRoUp paRaMeTeRs" + pure $ Just (SendGroup malgrp) - SendMessage cip -> do - sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "received ciphertext " <> cod - case dhKey of - Nothing -> do - slog "shared key not established" - pure Nothing - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "decrypted ciphertext: \"" <> cod <> "\"" - - let hourOfDestiny = case B8.findIndex C.isDigit msg of - Nothing -> error "did i fat-finger a digit?" - Just j -> BS.drop j msg - - gen <- liftIO dhGen - iv <- liftIO $ CU.bytes 16 gen - let nmsg = CU.lpkcs7 $ "confirmed, attacking at " <> hourOfDestiny - ncip = AES.encryptCbcAES128 iv k nmsg - ncod = B64.encodeBase64 ncip - slog $ "replying with ciphertext " <> ncod - pure $ Just (SendTerminal ncip) + AckGroup -> do + slog "rECeiVed aCK" + slog "ReLaYINg ACk" + pure (Just AckGroup) - SendTerminal cip -> do + SendParams grp pk -> do + slog "nOt eXPecTinG gRoUp and PublIc KeY" + pure Nothing + + -- only want to send bogus key on the first time + SendPublic pk -> do + slog $ "REceIvED pUBlic keY " <> renderkey pk + slog $ "SeNDing BoGuS kEy " <> renderkey malg + pure $ Just (SendPublic malg) + + cmd -> mitmeval cmd + +-- negotiated-group mitm protocol eval, g = p - 1 +malgeval' + :: Command + -> StateT Sesh IO (Maybe Command) +malgeval' = \case + AckGroup -> do + slog "rECeiVed aCK" + slog "ReLaYINg ACk" + pure (Just AckGroup) + + SendParams grp pk -> do + slog "nOt eXPecTinG gRoUp and PublIc KeY" + pure Nothing + + SendPublic pk -> do + slog $ "REceIvED pUBlic keY " <> renderkey pk sesh@Sesh {..} <- S.get - let cod = B64.encodeBase64 cip - slog $ "received ciphertext " <> cod - case dhKey of + case dhKeys of Nothing -> do - slog "shared key not established" - pure Nothing - Just k -> do - let Just msg = CU.unpkcs7 (AES.decryptCbcAES128 k cip) - cod = TE.decodeLatin1 msg - slog $ "decrypted ciphertext: \"" <> cod <> "\"" - pure Nothing - + S.put sesh { + dhKeys = Just (Keys 1 1) + } + slog $ "SeNDing BoGuS kEy " <> renderkey 1 + pure $ Just (SendPublic 1) + Just Keys {..} -> do + slog $ "ReLAyINg pUbliC KeY " <> renderkey pk + pure $ Just (SendPublic pk) + + cmd -> malgeval (p - 1) cmd + +genGroup :: Natural -> Natural -> StateT Sesh IO Group +genGroup p g = do + sesh <- S.get + let grp = Group p g + S.put sesh { + dhGroup = Just grp + } + pure grp + +genKeypair :: StateT Sesh IO Keys +genKeypair = do + sesh@Sesh {..} <- S.get + case dhGroup of + Nothing -> do + slog "missing group parameters" + liftIO SE.exitFailure + Just grp -> do + gen <- liftIO dhGen + per <- liftIO $ genpair grp gen + S.put sesh { + dhKeys = Just per + } + pure per + +deriveKey :: Natural -> StateT Sesh IO BS.ByteString +deriveKey pk = do + sesh@Sesh {..} <- S.get + let params = do + grp <- dhGroup + per <- dhKeys + pure (grp, per) + case params of + Nothing -> do + slog "missing group parameters or keypair" + liftIO SE.exitFailure + Just (grp, per) -> do + let key = derivekey grp per pk + S.put sesh { + dhKey = Just key + } + pure key + +encrypt :: BS.ByteString -> StateT Sesh IO BS.ByteString +encrypt msg = do + sesh@Sesh {..} <- S.get + case dhKey of + Nothing -> do + slog "missing shared key" + liftIO SE.exitFailure + Just k -> do + gen <- liftIO dhGen + iv <- liftIO $ CU.bytes 16 gen + let pad = CU.lpkcs7 msg + pure $ AES.encryptCbcAES128 iv k pad + +decrypt :: BS.ByteString -> StateT Sesh IO BS.ByteString +decrypt cip = do + sesh@Sesh {..} <- S.get + case dhKey of + Nothing -> do + slog "missing shared key" + liftIO SE.exitFailure + Just k -> do + case CU.unpkcs7 (AES.decryptCbcAES128 k cip) of + Nothing -> do + slog "couldn't decrypt ciphertext" + liftIO SE.exitFailure + Just msg -> pure msg + +renderkey :: Natural -> T.Text +renderkey = + B16.encodeBase16 + . BL.toStrict + . CS.bytestringDigest + . CS.sha1 + . DB.encode