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 +3/−3
- app/Main.hs +2/−7
- qr-imager.cabal +9/−2
- src/Data/QRCodes.hs +17/−51
- src/Data/QRCodes/Exe.hs +58/−0
- src/Data/QRCodes/Image.hs +18/−0
- src/Data/QRCodes/Signature.hs +32/−0
- src/Data/QRCodes/Utils.hs +25/−0
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)+