From c6239c19425b92724b09d7965e212a0ed856a609 Mon Sep 17 00:00:00 2001 From: Igor Pashev Date: Tue, 10 Oct 2017 16:11:34 +0300 Subject: Use hindent --- .hindent.yaml | 3 ++ cli/Main.hs | 51 +++++++++++++------------- nodejs/Main.hs | 25 +++++++------ src/Web/ZeroBin.hs | 77 ++++++++++++++++++++------------------- src/Web/ZeroBin/SJCL.hs | 93 +++++++++++++++++++++++++----------------------- src/Web/ZeroBin/Utils.hs | 20 +++++------ 6 files changed, 141 insertions(+), 128 deletions(-) create mode 100644 .hindent.yaml diff --git a/.hindent.yaml b/.hindent.yaml new file mode 100644 index 0000000..3dba089 --- /dev/null +++ b/.hindent.yaml @@ -0,0 +1,3 @@ +indent-size: 2 +line-length: 80 +force-trailing-newline: true diff --git a/cli/Main.hs b/cli/Main.hs index 643fd5f..f40966c 100644 --- a/cli/Main.hs +++ b/cli/Main.hs @@ -1,21 +1,26 @@ {-# LANGUAGE QuasiQuotes #-} -module Main where +module Main + ( main + ) where +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as C import Data.Version (showVersion) import Paths_zerobin (version) -- from cabal +import qualified System.Console.Docopt.NoTH as O import System.Environment (getArgs) import System.Exit (exitFailure) -import System.IO (stderr, hPutStrLn) +import System.IO (hPutStrLn, stderr) import Text.RawString.QQ (r) -import Web.ZeroBin (share, Expiration(..)) -import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as C -import qualified System.Console.Docopt.NoTH as O +import Web.ZeroBin (Expiration(..), share) usage :: String -usage = "zerobin " ++ showVersion version - ++ " pastes to 0bin services" ++ [r| +usage = + "zerobin " ++ + showVersion version ++ + " pastes to 0bin services" ++ + [r| zerobin prints URI to be shared or error message See http://0bin.net and https://paste.ec @@ -37,15 +42,14 @@ Examples: zerobin -b http://0bin.net hello paste to 0bin.net |] - getExpiration :: String -> Maybe Expiration getExpiration e = case e of - "once" -> Just Once - "day" -> Just Day - "week" -> Just Week + "once" -> Just Once + "day" -> Just Day + "week" -> Just Week "month" -> Just Month - _ -> Nothing + _ -> Nothing die :: String -> IO () die msg = do @@ -63,14 +67,13 @@ main = do doco <- O.parseUsageOrExit usage args <- O.parseArgsOrExit doco =<< getArgs if args `O.isPresent` O.longOption "help" - then putStrLn $ O.usage doco - else do - let get = O.getArgOrExitWith doco - bin <- args `get` O.longOption "bin" - expire <- args `get` O.longOption "expire" - text <- args `get` O.argument "TEXT" - cnt <- getContent (args `O.isPresent` O.longOption "file") text - case getExpiration expire of - Nothing -> die "invalid value for expiration" - Just e -> share bin e cnt >>= putStrLn - + then putStrLn $ O.usage doco + else do + let get = O.getArgOrExitWith doco + bin <- args `get` O.longOption "bin" + expire <- args `get` O.longOption "expire" + text <- args `get` O.argument "TEXT" + cnt <- getContent (args `O.isPresent` O.longOption "file") text + case getExpiration expire of + Nothing -> die "invalid value for expiration" + Just e -> share bin e cnt >>= putStrLn diff --git a/nodejs/Main.hs b/nodejs/Main.hs index 3cf74d9..48c67dd 100644 --- a/nodejs/Main.hs +++ b/nodejs/Main.hs @@ -1,18 +1,19 @@ {-# LANGUAGE OverloadedStrings #-} -module Main where +module Main + ( main + ) where -import System.Environment (getArgs) -import System.Process (callProcess) -import Web.ZeroBin.SJCL (encrypt) -import Web.ZeroBin.Utils (makePassword) import qualified Data.Aeson as JSON import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Lazy as L +import System.Environment (getArgs) +import System.Process (callProcess) +import Web.ZeroBin.SJCL (encrypt) +import Web.ZeroBin.Utils (makePassword) -- nodejs is a Debian's thing, others may have simple "node" - getText :: IO BS.ByteString getText = do args <- map C.pack `fmap` getArgs @@ -23,10 +24,8 @@ getText = do main :: IO () main = do password <- makePassword 32 - text <- getText - cont <- encrypt password text - callProcess "nodejs" [ "nodejs/decrypt.js" - , password - , C.unpack . L.toStrict $ JSON.encode cont - ] - + text <- getText + cont <- encrypt password text + callProcess + "nodejs" + ["nodejs/decrypt.js", password, C.unpack . L.toStrict $ JSON.encode cont] diff --git a/src/Web/ZeroBin.hs b/src/Web/ZeroBin.hs index f6ba051..0f32a50 100644 --- a/src/Web/ZeroBin.hs +++ b/src/Web/ZeroBin.hs @@ -8,40 +8,42 @@ High-level functions for posting to 0bin services like "http://0bin.net/paste/ZH6VyKXjDHAiPT8J#C6LLidGyHO7xt3xuDtsNHjZ77luualukEuJ25S6w/K1m" -} - {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-} -module Web.ZeroBin ( - Expiration(..), - ZeroBinError(..), - share -) where +module Web.ZeroBin + ( Expiration(..) + , ZeroBinError(..) + , share + ) where import Control.Exception (Exception) import Control.Exception.Base (throwIO) +import qualified Data.Aeson as JSON import Data.ByteString (ByteString) import Data.ByteString.Base64 (encode) +import qualified Data.ByteString.Char8 as C +import qualified Data.ByteString.Lazy as L import Data.Maybe (fromJust) import Data.Typeable (Typeable) import GHC.Generics (Generic) -import Web.ZeroBin.SJCL (encrypt, Content) -import Web.ZeroBin.Utils (makePassword) -import qualified Data.Aeson as JSON -import qualified Data.ByteString.Char8 as C -import qualified Data.ByteString.Lazy as L import qualified Network.HTTP.Conduit as HTTP +import Web.ZeroBin.SJCL (Content, encrypt) +import Web.ZeroBin.Utils (makePassword) -data Response = Response { - status :: String +data Response = Response + { status :: String , message :: Maybe String - , paste :: Maybe String + , paste :: Maybe String } deriving (Generic, Show) + instance JSON.FromJSON Response -- | 0bin error message -data ZeroBinError = ZeroBinError String +data ZeroBinError = + ZeroBinError String deriving (Show, Typeable) + instance Exception ZeroBinError -- | Expiration of a paste. @@ -50,44 +52,45 @@ instance Exception ZeroBinError -- You can verify your paste before sharing the link. -- Original does not support 'Week'. data Expiration - = Once -- ^ burn after reading - | Day -- ^ keep for 24 hours - | Week -- ^ for 7 days - | Month -- ^ for 30 days - | Never -- ^ for 100 years + = Once -- ^ burn after reading + | Day -- ^ keep for 24 hours + | Week -- ^ for 7 days + | Month -- ^ for 30 days + | Never -- ^ for 100 years form :: Expiration -> String -form Once = "burn_after_reading" -form Day = "1_day" -form Week = "1_week" +form Once = "burn_after_reading" +form Day = "1_day" +form Week = "1_week" form Month = "1_month" form Never = "never" post :: String -> Expiration -> Content -> IO String post bin ex ct = do req' <- HTTP.parseUrl $ bin ++ "/paste/create" - let req = HTTP.urlEncodedBody - [ (C.pack "expiration" , C.pack $ form ex) - , (C.pack "content" , L.toStrict $ JSON.encode ct) - ] req' + let req = + HTTP.urlEncodedBody + [ (C.pack "expiration", C.pack $ form ex) + , (C.pack "content", L.toStrict $ JSON.encode ct) + ] + req' manager <- HTTP.newManager HTTP.tlsManagerSettings response <- HTTP.httpLbs req manager let resp = fromJust . JSON.decode $ HTTP.responseBody response case status resp of "ok" -> return $ bin ++ "/paste/" ++ (fromJust . paste) resp - _ -> throwIO . ZeroBinError $ (fromJust . message) resp - + _ -> throwIO . ZeroBinError $ (fromJust . message) resp -- | Encrypts the plain data with a random password, -- post to 0bin and return the URI of a new paste. -- Can throw 'ZeroBinError' or 'Network.HTTP.Conduit.HttpException'. -share :: String -- ^ the address of 0bin, e. g. or - -> Expiration - -> ByteString -- ^ the plain data to encrypt and paste - -> IO String -- ^ the URI of paste +share :: + String -- ^ the address of 0bin, e. g. or + -> Expiration + -> ByteString -- ^ the plain data to encrypt and paste + -> IO String -- ^ the URI of paste share bin ex txt = do - pwd <- makePassword 33 - cnt <- encrypt pwd (encode txt) - uri <- post bin ex cnt + pwd <- makePassword 33 + cnt <- encrypt pwd (encode txt) + uri <- post bin ex cnt return $ uri ++ "#" ++ pwd - diff --git a/src/Web/ZeroBin/SJCL.hs b/src/Web/ZeroBin/SJCL.hs index a27ecb7..c4bc86e 100644 --- a/src/Web/ZeroBin/SJCL.hs +++ b/src/Web/ZeroBin/SJCL.hs @@ -6,37 +6,37 @@ Encryption compatible with >>> encrypt "secret-word" (pack "hello") Content {iv = "VxyuJRVtKJqhG2iR/sPjAQ", salt = "AhnDuP1CkTCBlQTHgw", ct = "cqr7/pMRXrcROmcgwA"} -} - {-# LANGUAGE DeriveGeneric #-} -module Web.ZeroBin.SJCL ( - Content(..), - encrypt -) where +module Web.ZeroBin.SJCL + ( Content(..) + , encrypt + ) where import Crypto.Cipher.AES (AES128) -import Crypto.Cipher.Types (ivAdd, blockSize, cipherInit, ecbEncrypt, ctrCombine, makeIV) +import Crypto.Cipher.Types + (blockSize, cipherInit, ctrCombine, ecbEncrypt, ivAdd, makeIV) import Crypto.Error (throwCryptoErrorIO) import Crypto.Hash.Algorithms (SHA256(..)) import Crypto.KDF.PBKDF2 (prfHMAC) +import qualified Crypto.KDF.PBKDF2 as PBKDF2 import Crypto.Number.Serialize (i2ospOf_) import Crypto.Random.Entropy (getEntropy) +import qualified Data.Aeson as JSON +import qualified Data.ByteArray as BA import Data.ByteString (ByteString) +import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as C import Data.Maybe (fromJust) import Data.Word (Word8) import GHC.Generics (Generic) import Web.ZeroBin.Utils (toWeb) -import qualified Crypto.KDF.PBKDF2 as PBKDF2 -import qualified Data.Aeson as JSON -import qualified Data.ByteArray as BA -import qualified Data.ByteString as BS -import qualified Data.ByteString.Char8 as C -- | Encrypted content. Each field is a 'toWeb'-encoded byte-string -data Content = Content { - iv :: String -- ^ random initialization vector (IV) +data Content = Content + { iv :: String -- ^ random initialization vector (IV) , salt :: String -- ^ random salt - , ct :: String -- ^ encrypted data + , ct :: String -- ^ encrypted data } deriving (Generic, Show) -- FIXME: http://stackoverflow.com/questions/33045350/unexpected-haskell-aeson-warning-no-explicit-implementation-for-tojson @@ -49,16 +49,21 @@ makeCipher = throwCryptoErrorIO . cipherInit -- https://github.com/bitwiseshiftleft/sjcl/blob/master/core/pbkdf2.js -- TODO: this is default, we can specify it explicitly for forward compatibility makeKey :: ByteString -> ByteString -> ByteString -makeKey = PBKDF2.generate (prfHMAC SHA256) - PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 16} +makeKey = + PBKDF2.generate + (prfHMAC SHA256) + PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 16} chunks :: Int -> ByteString -> [ByteString] chunks sz = split - where split b | cl <= sz = [b'] -- padded - | otherwise = b1 : split b2 - where cl = BS.length b - (b1, b2) = BS.splitAt sz b - b' = BS.take sz $ BS.append b (BS.replicate sz 0) + where + split b + | cl <= sz = [b'] -- padded + | otherwise = b1 : split b2 + where + cl = BS.length b + (b1, b2) = BS.splitAt sz b + b' = BS.take sz $ BS.append b (BS.replicate sz 0) lengthOf :: Int -> Word8 lengthOf = ceiling . (logBase 256 :: Float -> Float) . fromIntegral @@ -67,34 +72,34 @@ lengthOf = ceiling . (logBase 256 :: Float -> Float) . fromIntegral -- Follows with a 8-bytes tag. -- Uses 16-bytes cipher key generated from the password and a random 'salt' -- by PBKDF2-HMAC-SHA256 with 1000 iterations. -encrypt :: String -- ^ the password - -> ByteString -- ^ the plain data to encrypt - -> IO Content +encrypt :: + String -- ^ the password + -> ByteString -- ^ the plain data to encrypt + -> IO Content encrypt password plaintext = do - ivd <- getEntropy 16 -- XXX it is truncated to get the nonce below - slt <- getEntropy 13 -- arbitrary length + ivd <- getEntropy 16 -- XXX it is truncated to get the nonce below + slt <- getEntropy 13 -- arbitrary length cipher <- makeCipher $ makeKey (C.pack password) slt let tlen = 8 :: Word8 l = BS.length plaintext eL = max 2 (lengthOf l) nonce = BS.take (15 - fromIntegral eL) ivd - b0 = BS.concat [ - BS.pack [8*((tlen-2) `div` 2) + (eL-1)], - nonce, - i2ospOf_ (fromIntegral eL) (fromIntegral l) - ] - mac = foldl (\ a b -> ecbEncrypt cipher $ BA.xor a b) - (ecbEncrypt cipher b0) - (chunks (blockSize cipher) plaintext) + b0 = + BS.concat + [ BS.pack [8 * ((tlen - 2) `div` 2) + (eL - 1)] + , nonce + , i2ospOf_ (fromIntegral eL) (fromIntegral l) + ] + mac = + foldl + (\a b -> ecbEncrypt cipher $ BA.xor a b) + (ecbEncrypt cipher b0) + (chunks (blockSize cipher) plaintext) tag = BS.take (fromIntegral tlen) mac - a0 = BS.concat [ - BS.pack [eL - 1], - nonce, - BS.replicate (fromIntegral eL) 0 - ] + a0 = BS.concat [BS.pack [eL - 1], nonce, BS.replicate (fromIntegral eL) 0] a1iv = ivAdd (fromJust . makeIV $ a0) 1 - ciphtext = BS.append - (ctrCombine cipher a1iv plaintext) - (BA.xor (ecbEncrypt cipher a0) tag) - return Content { iv = toWeb ivd, salt = toWeb slt, ct = toWeb ciphtext } - + ciphtext = + BS.append + (ctrCombine cipher a1iv plaintext) + (BA.xor (ecbEncrypt cipher a0) tag) + return Content {iv = toWeb ivd, salt = toWeb slt, ct = toWeb ciphtext} diff --git a/src/Web/ZeroBin/Utils.hs b/src/Web/ZeroBin/Utils.hs index 559d9ec..70068aa 100644 --- a/src/Web/ZeroBin/Utils.hs +++ b/src/Web/ZeroBin/Utils.hs @@ -1,11 +1,10 @@ {-| Various utility functions -} - -module Web.ZeroBin.Utils ( - toWeb -, makePassword -) where +module Web.ZeroBin.Utils + ( toWeb + , makePassword + ) where import Crypto.Random.Entropy (getEntropy) import Data.ByteString (ByteString) @@ -13,12 +12,13 @@ import Data.ByteString.Base64 (encode) import Data.ByteString.Char8 (unpack) -- | Encodes to base64 and drops padding '='. -toWeb :: ByteString -- ^ the data to encode - -> String -- ^ base64 string without padding +toWeb :: + ByteString -- ^ the data to encode + -> String -- ^ base64 string without padding toWeb = takeWhile (/= '=') . unpack . encode -- | Makes a random password -makePassword :: Int -- ^ the number of bytes of entropy - -> IO String -- ^ random byte-string encoded by 'toWeb' +makePassword :: + Int -- ^ the number of bytes of entropy + -> IO String -- ^ random byte-string encoded by 'toWeb' makePassword n = toWeb `fmap` getEntropy n - -- cgit v1.2.3