packages feed

qr-imager 0.1.0.1 → 0.1.2.0

raw patch · 8 files changed

+164/−63 lines, 8 filesdep +MissingHdep +optparse-applicativedep +processdep ~base

Dependencies added: MissingH, optparse-applicative, process

Dependency ranges changed: base

Files

README.md view
@@ -2,10 +2,10 @@ This is a library to generate `.png` files from QR codes.  ##Dependencies-The library depends on the C library [https://github.com/fukuchi/libqrencode](libqrencode) which you will need to install separately.+The library depends on the C library [https://github.com/fukuchi/libqrencode](libqrencode) which you will need to install separately, as well as the C library `Zbar` from [here](https://github.com/ZBar/ZBar). You should also be able to get them from your distro.   ##Usage-The library exports main functions - `createQRCode` and `byteStringToQR` - and their secured/signed versions. The first takes any object that is an instance of `ToJSON` and writes an image to file, while the second takes a (strict) bytestring and writes it to file.+The library exports three main functions - `createQRCode`, `readQRString`, and `byteStringToQR` - and their secured/signed versions. The first takes any object that is an instance of `ToJSON` and writes an image to file, while the second takes filepath pointing to an image and returns the text in the QR code. The third takes a (strict) bytestring and writes it to file.  ##Executable @@ -20,6 +20,6 @@  ###Use -Compiling will generate an executable called `QRPipe` which reads from `stdin` and outputs a file as the second argument, e.g.+Compiling will generate an executable called `qrpipe` which reads from `stdin` and outputs a file as the second argument, e.g.  ```echo 'My name is:" | qrpipe "nametag.png"```
app/Main.hs view
@@ -1,11 +1,6 @@ {-#LANGUAGE OverloadedStrings #-} -import Data.QRCodes-import qualified Data.ByteString as B-import System.Environment (getArgs)+import Data.QRCodes.Exe (exec)  main :: IO ()-main = do-    pipeIn <- B.getContents-    filepath <- fmap (flip (!!) 0) getArgs-    byteStringToQRSec pipeIn filepath+main = exec
qr-imager.cabal view
@@ -1,5 +1,5 @@ name:                qr-imager-version:             0.1.0.1+version:             0.1.2.0 synopsis:            Library to generate QR codes from bytestrings and objects description:         Please see README.md homepage:            https://github.com/vmchale/QRImager#readme@@ -17,6 +17,10 @@ library   hs-source-dirs:      src   exposed-modules:     Data.QRCodes+                     , Data.QRCodes.Exe+  other-modules:       Data.QRCodes.Image+                     , Data.QRCodes.Signature+                     , Data.QRCodes.Utils   build-depends:       base >= 4.7 && < 5                      , aeson                      , JuicyPixels@@ -27,12 +31,15 @@                      , jose-jwt                      , directory                      , haskell-qrencode+                     , process+                     , MissingH+                     , optparse-applicative   default-language:    Haskell2010  executable qrpipe   hs-source-dirs:      app   main-is:             Main.hs-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -O3+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -O2   build-depends:       base                      , qr-imager                      , bytestring
src/Data/QRCodes.hs view
@@ -6,64 +6,32 @@                     , createQRCode                     , byteStringToQR                     , byteStringToQRSec+                    , readQRString+                    , readQRStrSec                     ) where  import Data.Aeson import Data.QRCode-import Codec.Picture.Types as T import Codec.Picture.Png (writePng)-import Data.Word (Word8)-import qualified Data.Vector.Storable as V import Data.ByteString.Lazy (toStrict)-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BSL-import Data.List (replicate)+import qualified Data.ByteString.Char8 as BS import Data.Char (toLower)-import Prelude as P-import Crypto.PubKey.RSA as Cr-import Jose.Jws-import System.Directory (doesFileExist) import Control.Lens.Tuple import Control.Lens (view)-import Jose.Jwt (unJwt, JwtError)-import Jose.Jwa (JwsAlg (RS512))-import Data.Either (either)-import Data.Bits ((.&.)) import Control.Applicative ((<$>))---- | Verify a signed token-checkSig :: BS.ByteString -> IO (Either JwtError BS.ByteString)-checkSig tok = do-    key <- read <$> readFile ".key.hk" :: IO (Cr.PublicKey, Cr.PrivateKey)-    let jws = rsaDecode (view _1 key) tok-    return $ fmap (view _2) jws---- | Sign a token-mkSig :: BS.ByteString -> IO BS.ByteString-mkSig string = do-    switch <- doesFileExist ".key.hk"-    if not switch then do-        putStrLn "generating key..."-        key <- Cr.generate 512 0x10001-        writeFile ".key.hk" (show key)-    else-        return ()-    key' <- read <$> readFile ".key.hk" :: IO (Cr.PublicKey, Cr.PrivateKey)-    signedToken <- rsaEncode RS512 (view _2 key') string-    let signed = fmap unJwt signedToken-    liftEither id (return <$> signed)+import System.Process+import Data.QRCodes.Utils+import Data.QRCodes.Signature+import Data.QRCodes.Image  -- | Creates a signed QR code from a strict bytestring byteStringToQRSec :: BS.ByteString -> FilePath -> IO ()-byteStringToQRSec string filepath = (flip byteStringToQR filepath) =<< (mkSig string)+byteStringToQRSec string filepath = (flip byteStringToQR filepath) =<< (((fmap preserveUpper) . mkSig) string)  -- | Creates a signed QR code from an object that is part of the ToJSON class createSecureQRCode :: (ToJSON a) => a -> FilePath -> IO () createSecureQRCode object = byteStringToQRSec (toStrict $ encode object) -liftEither :: (Show b, Monad m) => (t -> m a) -> Either b t -> m a-liftEither = either (error . show)- -- | Creates a QR code from an object that is part of the ToJSON class createQRCode :: (ToJSON a) => a -> FilePath -> IO () createQRCode object filepath = let input = toStrict $ encode object in byteStringToQR input filepath@@ -72,17 +40,15 @@ byteStringToQR :: BS.ByteString -> FilePath -> IO () byteStringToQR input filepath = do     smallMatrix <- toMatrix <$> encodeByteString input Nothing QR_ECLEVEL_H QR_MODE_EIGHT False-    let qrMatrix = fattenList 8 $ P.map (fattenList 8) smallMatrix+    let qrMatrix = fattenList 8 $ map (fattenList 8) smallMatrix     writePng filepath (encodePng qrMatrix) -encodePng :: [[Word8]] -> T.Image Word8-encodePng matrix = Image dim dim vector-    where dim    = P.length matrix-          vector = V.map ((*255) . swapWord) $ V.fromList $ P.concat matrix--fattenList :: Int -> [a] -> [a]-fattenList i l = P.concat $ P.foldr ((:) . (P.replicate i)) [] l+-- | given a filepath, read the QR code as a string in all lowercase+readQRString :: FilePath -> IO String+readQRString filepath = (map toLower) . init . (drop 8 . view _2) <$> readCreateProcessWithExitCode (shell $ "zbarimg " ++ filepath) "" -swapWord :: Word8 -> Word8-swapWord 1 = 0-swapWord 0 = 1+-- | given a filepath pointing to a QR code, get the contents & verify signature+readQRStrSec :: FilePath -> IO String+readQRStrSec filepath = do+    enc <- (map toLower) . init . (drop 8) . (view _2) <$> readCreateProcessWithExitCode (shell $ "zbarimg " ++ filepath) ""+    (fmap $ liftEither show) . checkSig . resolveUpper $ (BS.pack) enc
+ src/Data/QRCodes/Exe.hs view
@@ -0,0 +1,58 @@+module Data.QRCodes.Exe (exec) where++import Options.Applicative+import qualified Data.ByteString as B+import System.Environment (getArgs) --fix soon!+import Data.QRCodes++data Prog = Prog { cmd     :: Com+                 , secured :: Bool+                 , verify  :: Bool+                 , file    :: String} --whether to verify the encoding worked++data Com = Input | Output--whether to be writing *to* file or reading *from it++exec :: IO ()+exec = execParser full >>= act+    where +        full = info (helper <*> program)+            ( fullDesc+            <> progDesc "Read/Write QR Codes with files"+            <> header "qrpipe - QR utilities made in Haskell" )++act :: Prog -> IO ()+act (Prog Output True True filepath) = do+    pipeIn <- B.getContents+    byteStringToQRSec pipeIn filepath+    readQRStrSec filepath >>= print+act (Prog Output False True filepath) = do+    pipeIn <- B.getContents+    byteStringToQR pipeIn filepath+    readQRString filepath >>= print+act (Prog Output True False filepath) = do+    pipeIn <- B.getContents+    byteStringToQRSec pipeIn filepath+act (Prog Output False False filepath) = do+    pipeIn <- B.getContents+    byteStringToQR pipeIn filepath+act (Prog Input True _ filepath) = do+    readQRStrSec filepath >>= print+act (Prog Input False _ filepath) = do+    readQRString filepath >>= print++program :: Parser Prog+program = Prog+    <$> subparser+        ( command "write" (info (pure Output)+            ( progDesc "Create a QR Code from stdin" ))+        <> command "read" (info (pure Input)+            ( progDesc "Read a QR code from file" )))+    <*> switch+        ( long "signed"+        <> short 's'+        <> help "Whether to sign the QR code" )+    <*> switch+        ( long "verify"+        <> short 'v'+        <> help "Attempt to read the resultant file?" )+    <*> argument str (metavar "FILE")
+ src/Data/QRCodes/Image.hs view
@@ -0,0 +1,18 @@+module Data.QRCodes.Image where++import Data.Word (Word8)+import Codec.Picture.Types as T+import Prelude as P+import qualified Data.Vector.Storable as V++encodePng :: [[Word8]] -> T.Image Word8+encodePng matrix = Image dim dim vector+    where dim    = P.length matrix+          vector = V.map ((*255) . swapWord) $ V.fromList $ P.concat matrix++fattenList :: Int -> [a] -> [a]+fattenList i l = P.concat $ P.foldr ((:) . (P.replicate i)) [] l++swapWord :: Word8 -> Word8+swapWord 1 = 0+swapWord 0 = 1
+ src/Data/QRCodes/Signature.hs view
@@ -0,0 +1,32 @@+module Data.QRCodes.Signature where++import Jose.Jws+import qualified Data.ByteString.Char8 as BS+import Jose.Jwt (unJwt, JwtError)+import Crypto.PubKey.RSA as Cr+import Control.Lens+import System.Directory+import Jose.Jwa (JwsAlg (RS256))+import Data.QRCodes.Utils++-- | Verify a signed token+checkSig :: BS.ByteString -> IO (Either JwtError BS.ByteString)+checkSig tok = do+    key <- read <$> readFile ".key.hk" :: IO (Cr.PublicKey, Cr.PrivateKey)+    let jws = rsaDecode (view _1 key) tok+    return $ fmap (view _2) jws++-- | Sign a token (note that we map the string to all lowercase before signing since QR codes do not distinguish case once read)+mkSig :: BS.ByteString -> IO BS.ByteString+mkSig string = do+    switch <- doesFileExist ".key.hk"+    if not switch then do+        putStrLn "generating key..."+        key <- Cr.generate 256 0x10001+        writeFile ".key.hk" (show key)+    else+        return ()+    key' <- read <$> readFile ".key.hk" :: IO (Cr.PublicKey, Cr.PrivateKey)+    signedToken <- rsaEncode RS256 (view _2 key') string+    let signed = fmap unJwt signedToken+    liftEither id (return <$> signed)
+ src/Data/QRCodes/Utils.hs view
@@ -0,0 +1,25 @@+module Data.QRCodes.Utils where++import qualified Data.ByteString.Char8 as BS+import Data.Char (toLower, toUpper)+import Data.List.Utils (replace)++-- | function applied to byteStrings before saving to QR code so that uppercase/lowercase signatures can be preserverd+preserveUpper :: BS.ByteString -> BS.ByteString+preserveUpper = lift pU+    where pU = concatMap (\c -> if c `elem` ['A'..'Z'] then ((toLower c) : "---") else return c)++-- | resolve coded string to string with uppercase+resolveUpper :: BS.ByteString -> BS.ByteString+resolveUpper = lift rU+    where rU = foldr (.) id (map (\s -> replace (s : "---") ((pure . toUpper) s)) ['a'..'z'])++--kind of hacky and could be fixed in all likelihood+lift :: (String -> String) -> (BS.ByteString -> BS.ByteString)+lift f = BS.pack . f . BS.unpack++--liftIO = either (show) (((++) "success! ") . show)++liftEither :: (Show b, Monad m) => (t -> m a) -> Either b t -> m a+liftEither = either (error . show)+