diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Web/ZeroBin.hs | 36 | ||||
-rw-r--r-- | src/Web/ZeroBin/SJCL.hs | 26 | ||||
-rw-r--r-- | src/Web/ZeroBin/Utils.hs | 13 |
3 files changed, 60 insertions, 15 deletions
diff --git a/src/Web/ZeroBin.hs b/src/Web/ZeroBin.hs index 94c2b17..f6ba051 100644 --- a/src/Web/ZeroBin.hs +++ b/src/Web/ZeroBin.hs @@ -1,3 +1,14 @@ +{-| +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 #-} @@ -28,16 +39,22 @@ data Response = Response { } 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" @@ -60,7 +77,14 @@ post bin ex ct = do "ok" -> return $ bin ++ "/paste/" ++ (fromJust . paste) resp _ -> throwIO . ZeroBinError $ (fromJust . message) resp -share :: String -> Expiration -> ByteString -> IO 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 cnt <- encrypt pwd (encode txt) diff --git a/src/Web/ZeroBin/SJCL.hs b/src/Web/ZeroBin/SJCL.hs index faa2db0..60e8737 100644 --- a/src/Web/ZeroBin/SJCL.hs +++ b/src/Web/ZeroBin/SJCL.hs @@ -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.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 - , 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 @@ -53,9 +63,13 @@ chunks sz = split 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 diff --git a/src/Web/ZeroBin/Utils.hs b/src/Web/ZeroBin/Utils.hs index 8f29ec4..559d9ec 100644 --- a/src/Web/ZeroBin/Utils.hs +++ b/src/Web/ZeroBin/Utils.hs @@ -1,3 +1,7 @@ +{-| +Various utility functions +-} + module Web.ZeroBin.Utils ( toWeb , makePassword @@ -8,10 +12,13 @@ import Data.ByteString (ByteString) import Data.ByteString.Base64 (encode) import Data.ByteString.Char8 (unpack) - -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 +-- | 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 |