packages feed

crypto-srp-0.1.0.0: test/Crypto/PysrpCompatSpec.hs

{-# LANGUAGE OverloadedStrings #-}

module Crypto.PysrpCompatSpec
  ( spec
  )
where

import Crypto.SRP
  ( FromServer (..)
  , KnownAlgorithm (..)
  , PrimeGroup (..)
  , Results (..)
  , calcResults
  , fcPublicBytes
  , mkFromClient
  )
import qualified Data.ByteString as BS
import Numeric (readHex, showHex)
import Paths_crypto_srp (getDataFileName)
import System.Environment (getEnvironment)
import System.IO
  ( BufferMode (..)
  , hFlush
  , hGetLine
  , hPutStrLn
  , hSetBuffering
  )
import System.Process
  ( CreateProcess (..)
  , StdStream (..)
  , createProcess
  , proc
  , waitForProcess
  )
import Test.Hspec (Spec, describe, it, shouldBe)


encodeHex :: BS.ByteString -> String
encodeHex = concatMap padByte . BS.unpack
  where
    padByte b = case showHex b "" of
      [c] -> ['0', c]
      s -> s


decodeHex :: String -> BS.ByteString
decodeHex = BS.pack . go
  where
    go [] = []
    go (a : b : rest) = case readHex [a, b] of
      [(v, "")] -> v : go rest
      _ -> error $ "invalid hex pair: " ++ [a, b]
    go [_] = error "odd-length hex string"


spec :: Spec
spec = describe "pysrp compatibility" $
  it "completes a full SRP round-trip with pysrp as the server" $ do
    scriptPath <- getDataFileName "test/pysrp_server.py"
    baseEnv <- getEnvironment
    let creds = [("PYSRP_USER", "alice"), ("PYSRP_PASS", "hunter2")]
        processEnv = creds ++ baseEnv
        user = "alice"
        pass = "hunter2"

    fc <- mkFromClient user pass G2048

    (Just hin, Just hout, _, ph) <-
      createProcess
        (proc "python3" [scriptPath])
          { std_in = CreatePipe
          , std_out = CreatePipe
          , env = Just processEnv
          }
    hSetBuffering hin LineBuffering
    hSetBuffering hout LineBuffering

    hPutStrLn hin $ encodeHex $ fcPublicBytes fc
    hFlush hin

    line <- hGetLine hout
    let (saltBS, bBS) = case words line of
          [s, b] -> (decodeHex s, decodeHex b)
          _ -> error $ "unexpected pysrp_server.py output: " ++ line
        fs =
          FromServer
            { fsPublicBytes = bBS
            , fsSalt = saltBS
            , fsPrimeGroup = G2048
            , fsKnownAlgorithm = SHA256
            }

    case calcResults () fc fs of
      Nothing -> fail "calcResults returned Nothing (server public key was invalid)"
      Just results -> do
        hPutStrLn hin $ encodeHex $ rClientProof results
        hFlush hin

        m2Line <- hGetLine hout
        m2Line `shouldBe` encodeHex (rServerProof results)

        _ <- waitForProcess ph
        pure ()