packages feed

zerobin (empty) → 1.1.1

raw patch · 9 files changed

+460/−0 lines, 9 filesdep +aesondep +basedep +base64-bytestringsetup-changed

Dependencies added: aeson, base, base64-bytestring, bytestring, cryptonite, docopt, http-conduit, memory, process, raw-strings-qq, zerobin

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2015, Zalora South East Asia Pte. Ltd++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,77 @@+About+=====+This is a library and a command-line utility+to share secrets via ["zerobin"](https://github.com/sametmax/0bin)+sites like https://paste.ec+using client-side encryption with [SJCL](https://crypto.stanford.edu/sjcl/).++This library reimplements encryption part of [SJCL](https://crypto.stanford.edu/sjcl/)+allowing you to post secrets from Haskell programs and shell script.++Requirements+============+ZeroBin is written in Haskell with [GHC](http://www.haskell.org/ghc/).+All required Haskell libraries are listed in [zerobin.cabal](zerobin.cabal).+Use [cabal-install](http://www.haskell.org/haskellwiki/Cabal-Install)+to fetch and build all pre-requisites automatically.++Installation+============+    $ git clone https://github.com/zalora/zerobin.git+    $ cd zerobin+    $ cabal install++Command-line utility+====================+The command-line utility `zerobin` encrypts text or file,+post the encrypted data to https://paste.ec and+prints URI to be shared or error message:++    $ zerobin 'heinrich hertz'+    https://paste.ec/paste/1j3GBy-7#dg0PXHFglISOhXzRnU4KLWbSAh5jX5KjX4wZEiYM8QA6+++Type `zerobin --help` to see usage summary:++    Usage:+      zerobin [options] TEXT++    Options:+      -b, --bin=BIN   0bin service [default: https://paste.ec]+      -f, --file      Paste the content of file TEXT instead of plain TEXT+      -e, --expire=E  Set expiration of paste: once, day, week, month [default: day]++      -h, --help      Show this message++    Examples:+      zerobin hello                      paste "hello" for a day+      zerobin -f /etc/fstab              paste file /etc/fstab for a day+      zerobin -e once hello              paste "hello", it will burn after reading+      zerobin -b http://0bin.net hello   paste to 0bin.net+++Hacking+=======+There is a simple test program in the [./nodejs](./nodejs) directory.+It uses this library to encrypt a message and original SJCL+running by [Node.js](https://nodejs.org) to decrypt:++    $ git clone https://github.com/zalora/zerobin.git+    $ cd zerobin+    $ cabal install --dependencies-only+    $ cabal install -f nodejs --ghc-option="-Werror"+    $ # get nodejs and npm, e. g. on Debian; sudo apt-get install nodejs npm+    $ npm install sjcl+    $ ./dist/build/zerobin-nodejs/zerobin-nodejs+    heinrich hertz++Features/Bugs/TODOs+===================+1. [0bin](https://github.com/sametmax/0bin) supports images,+   `zerobin` can encrypt anything, but only plain text will be decrypted.+2. "Burn after reading" (`-e once`) really means "burn after two readings",+   because we do not redirect like browser does.+   You can verify your paste before sharing the link ;-)+3. http://0bin.net does not support `-e week`++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cli/Main.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE QuasiQuotes #-}++module Main where++import Data.Version (showVersion)+import Paths_zerobin (version) -- from cabal+import System.Environment (getArgs)+import System.Exit (exitFailure)+import System.IO (stderr, hPutStrLn)+import Text.RawString.QQ (r)+import ZeroBin (share, Expiration(..))+import qualified Data.ByteString as BS+import qualified Data.ByteString.Char8 as C+import qualified System.Console.Docopt.NoTH as O++usage :: String+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++Usage:+  zerobin [options] TEXT++Options:+  -b, --bin=BIN   0bin service [default: https://paste.ec]+  -f, --file      Paste the content of file TEXT instead of plain TEXT+  -e, --expire=E  Set expiration of paste: once, day, week, month [default: day]++  -h, --help      Show this message++Examples:+  zerobin hello                      paste "hello" for a day+  zerobin -f /etc/fstab              paste file /etc/fstab for a day+  zerobin -e once hello              paste "hello", it will burn after reading+  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+    "month" -> Just Month+    _       -> Nothing++die :: String -> IO ()+die msg = do+  hPutStrLn stderr $ "zerobin: " ++ msg+  exitFailure++getContent :: Bool -> String -> IO BS.ByteString+getContent asFile text =+  if not asFile+    then return $ C.pack text+    else BS.readFile text+++main :: IO ()+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  -> do+        rc <- share bin e cnt+        case rc of+          Left err -> die err+          Right uri -> putStrLn uri+
+ nodejs/Main.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import System.Environment (getArgs)+import System.Process (callProcess)+import ZeroBin.SJCL (encrypt)+import 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++-- nodejs is a Debian's thing, others may have simple "node"++getText :: IO BS.ByteString+getText = do+  args <- map C.pack `fmap` getArgs+  if null args+    then return "heinrich hertz"+    else return . BS.intercalate " " $ args++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+    ]+
+ src/ZeroBin.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE DeriveGeneric #-}++module ZeroBin (+  Expiration(..),+  share+) where++import Data.ByteString (ByteString)+import Data.ByteString.Base64 (encode)+import Data.Maybe (fromJust)+import GHC.Generics (Generic)+import ZeroBin.SJCL (encrypt, Content)+import 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++data Response = Response {+    status  :: String+  , message :: Maybe String+  , paste   :: Maybe String+  } deriving (Generic, Show)+instance JSON.FromJSON Response++data Expiration+  = Once +  | Day+  | Week+  | Month+  | Never++form :: Expiration -> String+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 (Either String 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'+  manager <- HTTP.newManager HTTP.tlsManagerSettings+  response <- HTTP.httpLbs req manager+  let resp = fromJust . JSON.decode $ HTTP.responseBody response+  case status resp of+    "ok" -> return . Right $+            bin ++ "/paste/" ++ (fromJust . paste) resp+    _    -> return . Left $+            (fromJust . message) resp++share :: String -> Expiration -> ByteString -> IO (Either String String)+share bin ex txt = do+  pwd  <- makePassword 33+  c    <- encrypt pwd (encode txt)+  append pwd `fmap` post bin ex c+  where+    append _ (Left e) = Left e+    append p (Right u) = Right $ u ++ "#" ++ p+
+ src/ZeroBin/SJCL.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DeriveGeneric #-}++module ZeroBin.SJCL (+  Content(..),+  encrypt+) where++import Crypto.Cipher.AES (AES256)+import Crypto.Cipher.Types (ivAdd, blockSize, cipherInit, ecbEncrypt, ctrCombine, makeIV)+import Crypto.Error (throwCryptoErrorIO)+import Crypto.Hash.Algorithms (SHA256(..))+import Crypto.KDF.PBKDF2 (prfHMAC)+import Crypto.Number.Serialize (i2ospOf_)+import Crypto.Random.Entropy (getEntropy)+import Data.ByteString (ByteString)+import Data.Maybe (fromJust)+import Data.Word (Word8)+import GHC.Generics (Generic)+import 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++data Content = Content {+    iv   :: String+  , salt :: String+  , ct   :: String+  } deriving (Generic, Show)++-- FIXME: http://stackoverflow.com/questions/33045350/unexpected-haskell-aeson-warning-no-explicit-implementation-for-tojson+instance JSON.ToJSON Content where+  toJSON = JSON.genericToJSON JSON.defaultOptions++makeCipher :: ByteString -> IO AES256+makeCipher = throwCryptoErrorIO . cipherInit++-- SJCL uses PBKDF2-HMAC-SHA256 with 1000 iterations, 32 bytes length,+-- but the output is truncated down to 16 bytes.+-- 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 pwd slt = BS.take 16 $ PBKDF2.generate (prfHMAC SHA256)+  PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 32}+  pwd slt+++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)++lengthOf :: Int -> Word8+lengthOf = ceiling . (logBase 256 :: Float -> Float) . fromIntegral++-- Ref. https://tools.ietf.org/html/rfc3610+-- SJCL uses 64-bit tag (8 bytes)+encrypt :: String -> ByteString -> IO Content+encrypt password plaintext = do+  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)+      tag = BS.take (fromIntegral tlen) mac+      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 }+
+ src/ZeroBin/Utils.hs view
@@ -0,0 +1,19 @@+module ZeroBin.Utils (+  toWeb+, makePassword+) where++import Crypto.Random.Entropy (getEntropy)+import Data.ByteString (ByteString)+import Data.ByteString.Base64 (encode)+import Data.ByteString.Char8 (unpack)+import Data.Char (isAlphaNum)+++toWeb :: ByteString -> String+toWeb = takeWhile (/= '=') . unpack . encode++makePassword :: Int -> IO String+makePassword n = (map (\c -> if isAlphaNum c then c else 'X')+                  . toWeb) `fmap` getEntropy n+
+ zerobin.cabal view
@@ -0,0 +1,75 @@+name: zerobin+version: 1.1.1+synopsis: Post to 0bin services+description: Post encrypted content to 0bin sites+  like http://0bin.net or https://paste.ec+license: MIT+license-file: LICENSE+author: Igor Pashev+maintainer: Igor Pashev <pashev.igor@gmail.com>+copyright: 2015, Zalora South East Asia Pte. Ltd+category: Cryptography, Web+build-type: Simple+extra-source-files: README.md+cabal-version: >= 1.20++source-repository head+  type: git+  location: https://github.com/zalora/zerobin.git++flag nodejs+    description: Build a test program for decrypting with Node.js and SJCL.+                 You need Node.js and SJCL installed (via NPM for example)+    default: False++flag cli+    description: Build a command-line utility. You can use it in shell scripts+    default: True++library+    default-language: Haskell2010+    ghc-options: -Wall+    hs-source-dirs: src+    build-depends:+      aeson             >= 0.10,+      base              >= 4.7 && < 5,+      base64-bytestring >= 1.0,+      bytestring        >= 0.10.6.0,+      cryptonite        >= 0.8,+      http-conduit      >= 2.1.8,+      memory            >= 0.10+    exposed-modules:+      ZeroBin,+      ZeroBin.SJCL,+      ZeroBin.Utils++executable zerobin+    default-language: Haskell2010+    ghc-options: -Wall -static+    hs-source-dirs: cli+    main-is: Main.hs+    if flag(cli)+      build-depends:+        base           >= 4.7 && < 5,+        bytestring     >= 0.10.6.0,+        docopt         >= 0.7.0.4,+        zerobin,+        raw-strings-qq >= 1.0.2+    else+      buildable: False++executable zerobin-nodejs+    default-language: Haskell2010+    ghc-options: -Wall+    hs-source-dirs: nodejs+    main-is: Main.hs+    if flag(nodejs)+      build-depends:+        aeson      >= 0.10,+        base       >= 4.7 && < 5,+        bytestring >= 0.10.6.0,+        zerobin,+        process    >= 1.3.0.0+    else+      buildable: False+