packages feed

moesocks-0.1.0.21: src/Network/MoeSocks/Encrypt.hs

-- Heavily inspired by
-- https://github.com/rnons/shadowsocks-haskell/blob/86bd47d474df6800e1f3706b33ae9e8f80696d8f/Shadowsocks/Encrypt.hs


{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

module Network.MoeSocks.Encrypt where

import Control.Lens
import Control.Monad.Except
import Crypto.Hash.MD5 (hash)
import Data.ByteString (ByteString)
import Data.Map
import Data.Monoid ((<>))
import Data.Text (Text)
import Data.Text.Lens
import Data.Text.Strict.Lens (utf8)
import OpenSSL (withOpenSSL)
import OpenSSL.EVP.Cipher (getCipherByName, CryptoMode(..))
import OpenSSL.Random (randBytes)
import Prelude hiding ((-))
import qualified Data.ByteString as S
import qualified Data.Strict as S
import qualified OpenSSL.EVP.Internal as E

-- BEGIN backports

infixr 0 -
(-) :: (a -> b) -> a -> b
(-) = ($)

-- END backports

type KeyLength = Int
type IV_Length = Int

type Cipher = S.Maybe ByteString -> IO ByteString 
type CipherBuilder = ByteString -> IO Cipher
type CipherBox = (Int, IO ByteString, CipherBuilder, CipherBuilder)

type MaybeT_IO = ExceptT () IO

methods :: Map Text (KeyLength, IV_Length)
methods = fromList
    [ ("aes-128-cfb", (16, 16))
    , ("aes-192-cfb", (24, 16))
    , ("aes-256-cfb", (32, 16))
    , ("bf-cfb", (16, 8))
    , ("camellia-128-cfb", (16, 16))
    , ("camellia-192-cfb", (24, 16))
    , ("camellia-256-cfb", (32, 16))
    , ("cast5-cfb", (16, 8))
    , ("des-cfb", (8, 8))
    , ("idea-cfb", (16, 8))
    , ("rc2-cfb", (16, 8))
    , ("rc4", (16, 0))
    , ("seed-cfb", (16, 16))
    ]


hashKey :: ByteString -> Int -> Int -> ByteString
hashKey aPassword aKeyLen a_IV_len = loop mempty mempty 
  where
    _stopLength = aKeyLen + a_IV_len

    loop :: ByteString -> ByteString -> ByteString
    loop _lastHashedBytes _accumHashedBytes
      | S.length _accumHashedBytes >= _stopLength 
        = S.take aKeyLen _accumHashedBytes
      | otherwise = let _new = hash - _lastHashedBytes <> aPassword
                    in
                    loop _new - _accumHashedBytes <> _new


ssl :: IO a -> IO a
ssl = withOpenSSL

eitherToMaybe :: Either a b -> Maybe b
eitherToMaybe (Left _) = Nothing
eitherToMaybe (Right x) = Just x

getMaybe :: Maybe a -> MaybeT_IO a
getMaybe Nothing = throwError ()
getMaybe (Just x) = pure x

mio :: IO (Maybe a) -> MaybeT_IO a
mio = (>>= getMaybe) . liftIO

identityCipher :: Cipher
identityCipher = pure . S.fromMaybe mempty

initBuilder :: Text -> Text -> IO (Maybe CipherBox)
initBuilder aMethod aPassword 
  | aMethod == "none" = let constCipher = const - pure identityCipher
                        in
                        pure - Just (0, pure mempty, constCipher, constCipher)

  | otherwise =
      fmap eitherToMaybe - runExceptT - initBuilder' aMethod aPassword

initBuilder' :: Text -> Text -> MaybeT_IO CipherBox
initBuilder' aMethod aPassword = do
  _method <- mio - ssl - getCipherByName - aMethod ^. _Text

  (_keyLength, _IV_Length) <- getMaybe - methods ^? ix aMethod

  let _hashed = hashKey (review utf8 aPassword) _keyLength _IV_Length
      _IV_Maker = ssl - randBytes _IV_Length

      _encryptBuilder :: ByteString -> IO Cipher
      _encryptBuilder _iv = do
          _ctx <- ssl - E.cipherInitBS _method _hashed _iv Encrypt

          let _encrypt :: Cipher
              _encrypt = \case
                  S.Nothing -> ssl - E.cipherFinalBS _ctx
                  S.Just _bytes -> do
                    ssl - E.cipherUpdateBS _ctx _bytes

          pure _encrypt

      _decryptBuilder :: ByteString -> IO Cipher
      _decryptBuilder _iv = do
          _ctx <- ssl - E.cipherInitBS _method _hashed _iv Decrypt

          let _decrypt :: Cipher
              _decrypt = \case
                  S.Nothing -> ssl - E.cipherFinalBS _ctx
                  S.Just _bytes -> do
                    ssl - E.cipherUpdateBS _ctx _bytes 
          
          pure _decrypt
          

  pure - (_IV_Length, _IV_Maker, _encryptBuilder, _decryptBuilder)