packages feed

zerobin 1.1.1 → 1.2.0

raw patch · 10 files changed

+182/−182 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- ZeroBin: Day :: Expiration
- ZeroBin: Month :: Expiration
- ZeroBin: Never :: Expiration
- ZeroBin: Once :: Expiration
- ZeroBin: Week :: Expiration
- ZeroBin: data Expiration
- ZeroBin: instance Data.Aeson.Types.Class.FromJSON ZeroBin.Response
- ZeroBin: instance GHC.Generics.Constructor ZeroBin.C1_0Response
- ZeroBin: instance GHC.Generics.Datatype ZeroBin.D1Response
- ZeroBin: instance GHC.Generics.Generic ZeroBin.Response
- ZeroBin: instance GHC.Generics.Selector ZeroBin.S1_0_0Response
- ZeroBin: instance GHC.Generics.Selector ZeroBin.S1_0_1Response
- ZeroBin: instance GHC.Generics.Selector ZeroBin.S1_0_2Response
- ZeroBin: instance GHC.Show.Show ZeroBin.Response
- ZeroBin: share :: String -> Expiration -> ByteString -> IO (Either String String)
- ZeroBin.SJCL: Content :: String -> String -> String -> Content
- ZeroBin.SJCL: [ct] :: Content -> String
- ZeroBin.SJCL: [iv] :: Content -> String
- ZeroBin.SJCL: [salt] :: Content -> String
- ZeroBin.SJCL: data Content
- ZeroBin.SJCL: encrypt :: String -> ByteString -> IO Content
- ZeroBin.SJCL: instance Data.Aeson.Types.Class.ToJSON ZeroBin.SJCL.Content
- ZeroBin.SJCL: instance GHC.Generics.Constructor ZeroBin.SJCL.C1_0Content
- ZeroBin.SJCL: instance GHC.Generics.Datatype ZeroBin.SJCL.D1Content
- ZeroBin.SJCL: instance GHC.Generics.Generic ZeroBin.SJCL.Content
- ZeroBin.SJCL: instance GHC.Generics.Selector ZeroBin.SJCL.S1_0_0Content
- ZeroBin.SJCL: instance GHC.Generics.Selector ZeroBin.SJCL.S1_0_1Content
- ZeroBin.SJCL: instance GHC.Generics.Selector ZeroBin.SJCL.S1_0_2Content
- ZeroBin.SJCL: instance GHC.Show.Show ZeroBin.SJCL.Content
- ZeroBin.Utils: makePassword :: Int -> IO String
- ZeroBin.Utils: toWeb :: ByteString -> String
+ Web.ZeroBin: Day :: Expiration
+ Web.ZeroBin: Month :: Expiration
+ Web.ZeroBin: Never :: Expiration
+ Web.ZeroBin: Once :: Expiration
+ Web.ZeroBin: Week :: Expiration
+ Web.ZeroBin: data Expiration
+ Web.ZeroBin: instance Data.Aeson.Types.Class.FromJSON Web.ZeroBin.Response
+ Web.ZeroBin: instance GHC.Generics.Constructor Web.ZeroBin.C1_0Response
+ Web.ZeroBin: instance GHC.Generics.Datatype Web.ZeroBin.D1Response
+ Web.ZeroBin: instance GHC.Generics.Generic Web.ZeroBin.Response
+ Web.ZeroBin: instance GHC.Generics.Selector Web.ZeroBin.S1_0_0Response
+ Web.ZeroBin: instance GHC.Generics.Selector Web.ZeroBin.S1_0_1Response
+ Web.ZeroBin: instance GHC.Generics.Selector Web.ZeroBin.S1_0_2Response
+ Web.ZeroBin: instance GHC.Show.Show Web.ZeroBin.Response
+ Web.ZeroBin: share :: String -> Expiration -> ByteString -> IO (Either String String)
+ Web.ZeroBin.SJCL: Content :: String -> String -> String -> Content
+ Web.ZeroBin.SJCL: [ct] :: Content -> String
+ Web.ZeroBin.SJCL: [iv] :: Content -> String
+ Web.ZeroBin.SJCL: [salt] :: Content -> String
+ Web.ZeroBin.SJCL: data Content
+ Web.ZeroBin.SJCL: encrypt :: String -> ByteString -> IO Content
+ Web.ZeroBin.SJCL: instance Data.Aeson.Types.Class.ToJSON Web.ZeroBin.SJCL.Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Constructor Web.ZeroBin.SJCL.C1_0Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Datatype Web.ZeroBin.SJCL.D1Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Generic Web.ZeroBin.SJCL.Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Selector Web.ZeroBin.SJCL.S1_0_0Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Selector Web.ZeroBin.SJCL.S1_0_1Content
+ Web.ZeroBin.SJCL: instance GHC.Generics.Selector Web.ZeroBin.SJCL.S1_0_2Content
+ Web.ZeroBin.SJCL: instance GHC.Show.Show Web.ZeroBin.SJCL.Content
+ Web.ZeroBin.Utils: makePassword :: Int -> IO String
+ Web.ZeroBin.Utils: toWeb :: ByteString -> String

Files

README.md view
@@ -6,7 +6,7 @@ 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.+allowing you to post secrets from Haskell programs and shell scripts.  Requirements ============
cli/Main.hs view
@@ -8,7 +8,7 @@ import System.Exit (exitFailure) import System.IO (stderr, hPutStrLn) import Text.RawString.QQ (r)-import ZeroBin (share, Expiration(..))+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
nodejs/Main.hs view
@@ -4,8 +4,8 @@  import System.Environment (getArgs) import System.Process (callProcess)-import ZeroBin.SJCL (encrypt)-import ZeroBin.Utils (makePassword)+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
+ src/Web/ZeroBin.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE DeriveGeneric #-}++module Web.ZeroBin (+  Expiration(..),+  share+) where++import Data.ByteString (ByteString)+import Data.ByteString.Base64 (encode)+import Data.Maybe (fromJust)+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++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/Web/ZeroBin/SJCL.hs view
@@ -0,0 +1,91 @@+{-# LANGUAGE DeriveGeneric #-}++module Web.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 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++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/Web/ZeroBin/Utils.hs view
@@ -0,0 +1,19 @@+module Web.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+
− src/ZeroBin.hs
@@ -1,64 +0,0 @@-{-# 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
@@ -1,91 +0,0 @@-{-# 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
@@ -1,19 +0,0 @@-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
@@ -1,5 +1,5 @@ name: zerobin-version: 1.1.1+version: 1.2.0 synopsis: Post to 0bin services description: Post encrypted content to 0bin sites   like http://0bin.net or https://paste.ec@@ -39,9 +39,9 @@       http-conduit      >= 2.1.8,       memory            >= 0.10     exposed-modules:-      ZeroBin,-      ZeroBin.SJCL,-      ZeroBin.Utils+      Web.ZeroBin,+      Web.ZeroBin.SJCL,+      Web.ZeroBin.Utils  executable zerobin     default-language: Haskell2010