packages feed

zerobin 1.2.0 → 1.5.0

raw patch · 5 files changed

+81/−42 lines, 5 files

Files

cli/Main.hs view
@@ -72,9 +72,5 @@     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+      Just e  -> share bin e cnt >>= putStrLn 
src/Web/ZeroBin.hs view
@@ -1,13 +1,29 @@+{-|+High-level functions for posting to 0bin services like+<http://0bin.net> or <http://paste.ec>.++ >>> import Web.ZeroBin+ >>> import Data.ByteString.Char8+ >>> share "http://0bin.net" Day (pack "hello")+"http://0bin.net/paste/ZH6VyKXjDHAiPT8J#C6LLidGyHO7xt3xuDtsNHjZ77luualukEuJ25S6w/K1m"++-}++{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveGeneric #-}  module Web.ZeroBin (   Expiration(..),+  ZeroBinError(..),   share ) where +import Control.Exception (Exception)+import Control.Exception.Base (throwIO) import Data.ByteString (ByteString) import Data.ByteString.Base64 (encode) import Data.Maybe (fromJust)+import Data.Typeable (Typeable) import GHC.Generics (Generic) import Web.ZeroBin.SJCL (encrypt, Content) import Web.ZeroBin.Utils (makePassword)@@ -23,12 +39,22 @@   } deriving (Generic, Show) instance JSON.FromJSON Response +-- | 0bin error message+data ZeroBinError = ZeroBinError String+  deriving (Show, Typeable)+instance Exception ZeroBinError++-- | Expiration of a paste.+--   "Burn after reading" really means "burn after two readings",+--   because we do not redirect to the paste like a browser does.+--   You can verify your paste before sharing the link.+--   Original <http://0bin.net> does not support 'Week'. data Expiration-  = Once -  | Day-  | Week-  | Month-  | Never+  = 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"@@ -37,7 +63,7 @@ form Month = "1_month" form Never = "never" -post :: String -> Expiration -> Content -> IO (Either String String)+post :: String -> Expiration -> Content -> IO String post bin ex ct = do   req' <- HTTP.parseUrl $ bin ++ "/paste/create"   let req = HTTP.urlEncodedBody@@ -48,17 +74,20 @@   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+    "ok" -> return $ bin ++ "/paste/" ++ (fromJust . paste) resp+    _    -> throwIO . ZeroBinError $ (fromJust . message) resp -share :: String -> Expiration -> ByteString -> IO (Either String String)++-- | 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. <http://0bin.net> or <https://paste.ec>+      -> Expiration+      -> ByteString  -- ^ the plain data to encrypt and paste+      -> IO String   -- ^ the URI of paste 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+  cnt  <- encrypt pwd (encode txt)+  uri  <- post bin ex cnt+  return $ uri ++ "#" ++ pwd 
src/Web/ZeroBin/SJCL.hs view
@@ -1,3 +1,12 @@+{-|+Encryption compatible with <https://crypto.stanford.edu/sjcl/ SJCL>++ >>> import Web.ZeroBin.SJCL+ >>> import Data.ByteString.Char8+ >>> encrypt "secret-word" (pack "hello")+Content {iv = "VxyuJRVtKJqhG2iR/sPjAQ", salt = "AhnDuP1CkTCBlQTHgw", ct = "cqr7/pMRXrcROmcgwA"}+-}+ {-# LANGUAGE DeriveGeneric #-}  module Web.ZeroBin.SJCL (@@ -23,10 +32,11 @@ 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-  , salt :: String-  , ct   :: String+    iv   :: String -- ^ random initialization vector (IV)+  , salt :: String -- ^ random salt+  , ct   :: String -- ^ encrypted data   } deriving (Generic, Show)  -- FIXME: http://stackoverflow.com/questions/33045350/unexpected-haskell-aeson-warning-no-explicit-implementation-for-tojson@@ -36,16 +46,11 @@ 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+-- 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-+makeKey = PBKDF2.generate (prfHMAC SHA256)+  PBKDF2.Parameters {PBKDF2.iterCounts = 1000, PBKDF2.outputLength = 16}  chunks :: Int -> ByteString -> [ByteString] chunks sz = split@@ -58,9 +63,13 @@ 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+-- | <https://crypto.stanford.edu/sjcl/ SJCL>-compatible encryption function.+--   Follows <https://tools.ietf.org/html/rfc3610 RFC3610> 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 password plaintext = do   ivd    <- getEntropy 16 -- XXX it is truncated to get the nonce below   slt    <- getEntropy 13 -- arbitrary length
src/Web/ZeroBin/Utils.hs view
@@ -1,3 +1,7 @@+{-|+Various utility functions+-}+ module Web.ZeroBin.Utils (   toWeb , makePassword@@ -7,13 +11,14 @@ import Data.ByteString (ByteString) import Data.ByteString.Base64 (encode) import Data.ByteString.Char8 (unpack)-import Data.Char (isAlphaNum) --toWeb :: ByteString -> String+-- | Encodes to base64 and drops padding '='.+toWeb :: ByteString -- ^ the data to encode+      -> String     -- ^ base64 string without padding toWeb = takeWhile (/= '=') . unpack . encode -makePassword :: Int -> IO String-makePassword n = (map (\c -> if isAlphaNum c then c else 'X')-                  . toWeb) `fmap` getEntropy n+-- | Makes a random password+makePassword :: Int       -- ^ the number of bytes of entropy+             -> IO String -- ^ random byte-string encoded by 'toWeb'+makePassword n = toWeb `fmap` getEntropy n 
zerobin.cabal view
@@ -1,5 +1,5 @@ name: zerobin-version: 1.2.0+version: 1.5.0 synopsis: Post to 0bin services description: Post encrypted content to 0bin sites   like http://0bin.net or https://paste.ec