{-# Language OverloadedStrings, ImportQualifiedPost #-} module Client.Authentication.Ecdh ( -- * Phase type Phase1, -- * Mechanism details mechanismName, -- * Transition functions clientFirst, clientResponse, ) where import Control.Monad (guard) import Crypto.Curve25519.Pure qualified as Curve import Data.Bits (xor) import Data.ByteString (ByteString) import Data.ByteString qualified as B import Data.ByteString.Base64 qualified as B64 import Data.Text (Text) import Data.Text.Encoding qualified as Text import Irc.Commands (AuthenticatePayload (AuthenticatePayload)) import OpenSSL.EVP.Digest ( digestBS, getDigestByName, hmacBS, Digest ) import System.IO.Unsafe ( unsafePerformIO ) newtype Phase1 = Phase1 Curve.PrivateKey mechanismName :: Text mechanismName = "ECDH-X25519-CHALLENGE" clientFirst :: Maybe Text -> Text -> Text -> Maybe (AuthenticatePayload, Phase1) clientFirst mbAuthz authc privateKeyText = case Curve.importPrivate <$> B64.decode (Text.encodeUtf8 privateKeyText) of Right (Just private) -> Just (AuthenticatePayload payload, Phase1 private) _ -> Nothing where payload = case mbAuthz of Nothing -> Text.encodeUtf8 authc Just authz -> Text.encodeUtf8 authc <> "\0" <> Text.encodeUtf8 authz clientResponse :: Phase1 -> ByteString {- ^ server response -} -> Maybe AuthenticatePayload {- ^ client response -} clientResponse (Phase1 privateKey) serverMessage = do let (serverPubBS, (sessionSalt, maskedChallenge)) = B.splitAt 32 <$> B.splitAt 32 serverMessage guard (B.length maskedChallenge == 32) serverPublic <- Curve.importPublic serverPubBS let sharedSecret = Curve.makeShared privateKey serverPublic let clientPublic = Curve.generatePublic privateKey let ikm = digestBS sha256 $ sharedSecret <> Curve.exportPublic clientPublic <> serverPubBS let prk = hmacBS sha256 sessionSalt ikm let betterSecret = hmacBS sha256 prk "ECDH-X25519-CHALLENGE\1" let sessionChallenge = B.pack (B.zipWith xor maskedChallenge betterSecret) Just $! AuthenticatePayload sessionChallenge sha256 :: Digest Just sha256 = unsafePerformIO (getDigestByName "SHA256")