packages feed

qr-imager 0.2.0.0 → 0.2.1.0

raw patch · 6 files changed

+122/−36 lines, 6 files

Files

README.md view
@@ -2,11 +2,13 @@ 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, 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. +The library depends on the C library [libqrencode](https://github.com/fukuchi/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 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.+## Library+The library can be used via the exported functions `createQRCode`, `byteStringToQR` and `readQRString`, plus their signed counterparts. The first two export to ".png" while the third can be used on any image format `Zbar` supports. +The functions `bsToImg` and `objToImg` output JuicyPixels images for your further manipulation.+ ## Executable  ### Installation@@ -32,5 +34,3 @@ qrpipe read "nametag.png" ``` -## Library-The library can be used via the exported functions `createQRCode`, `byteStringToQR` and `readQRString`, plus their signed counterparts. 
qr-imager.cabal view
@@ -1,5 +1,5 @@ name:                qr-imager-version:             0.2.0.0+version:             0.2.1.0 synopsis:            Library to generate QR codes from bytestrings and objects description:         Please see README.md homepage:            https://github.com/vmchale/QRImager#readme@@ -18,8 +18,8 @@   hs-source-dirs:      src   exposed-modules:     Data.QRCodes                      , Data.QRCodes.Exe-  other-modules:       Data.QRCodes.Image-                     , Data.QRCodes.Signature+                     , Data.QRCodes.Image+  other-modules:       Data.QRCodes.Signature                      , Data.QRCodes.Utils   build-depends:       base >= 4.7 && < 5                      , aeson@@ -39,7 +39,7 @@ executable qrpipe   hs-source-dirs:      app   main-is:             Main.hs-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -O2+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N   build-depends:       base                      , qr-imager   default-language:    Haskell2010
src/Data/QRCodes.hs view
@@ -3,18 +3,20 @@  -- | Module providing several functions for creating QR codes and their signed counterparts module Data.QRCodes (-- * Functions on objects-                      createSecureQRCode-                    , createQRCode+                      createQRCode+                    , createSecureQRCode+                    , createSecureQRCode'                     -- * Functions for `ByteStrings`                     , byteStringToQR                     , byteStringToQRSec+                    , byteStringToQRSec'                     -- * functions to read QR codes                     , readQRString                     , readQRStrSec+                    , readQRStrSec'                     ) where  import Data.Aeson-import Data.QRCode import Codec.Picture.Png (writePng) import Data.ByteString.Lazy (toStrict) import qualified Data.ByteString.Char8 as BS@@ -26,18 +28,32 @@ import Data.QRCodes.Utils import Data.QRCodes.Signature import Data.QRCodes.Image+import Data.Word (Word8)+import Crypto.PubKey.RSA --- | Creates a signed QR code from a strict bytestring and path to keyfile/path where the keyfile should be generated.+-- | Creates a signed QR code from a strict bytestring and path to keyfile/path.+-- If the keyfile does not already exist it will be generated, otherwise it will be read.+-- -- Note that QR codes may only contain a small number of characters, so encrypting can sometimes make an object too big to encode. -- -- > byteStringToQRSec (BS.pack "hello") ".key.hk" "qrcode.png" byteStringToQRSec :: BS.ByteString -> FilePath -> FilePath -> IO ()-byteStringToQRSec string keyfile filepath = (flip byteStringToQR filepath) =<< (((fmap preserveUpper) . (flip mkSig keyfile)) string)+byteStringToQRSec string keyfile filepath = (flip byteStringToQR filepath) =<< (((fmap preserveUpper) . (flip mkSigFile keyfile)) string) +-- | Create a signed QR code from a strict `ByteString` and a key+--+-- > byteStringToQRSec' (BS.pack "Vanessa") (generate 256 0x10001)+byteStringToQRSec' :: BS.ByteString -> (PublicKey, PrivateKey) -> FilePath -> IO ()+byteStringToQRSec' string key filepath = (flip byteStringToQR filepath) =<< (((fmap preserveUpper) . (flip mkSig key)) string)+ -- | Creates a signed QR code from an object that is part of the ToJSON class createSecureQRCode :: (ToJSON a) => a -> FilePath -> FilePath -> IO () createSecureQRCode object = byteStringToQRSec (toStrict $ encode object) +-- | Creates a signed QR code from an object that is part of the ToJSON class+createSecureQRCode' :: (ToJSON a) => a -> (PublicKey, PrivateKey) -> FilePath -> IO ()+createSecureQRCode' object = byteStringToQRSec' (toStrict $ encode object)+ -- | Creates a QR code from an object that is part of the ToJSON class -- -- > createQRCode userRecord "user-231.png"@@ -46,10 +62,7 @@  -- | Creates a QR code from a strict bytestring 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 $ map (fattenList 8) smallMatrix-    writePng filepath (encodePng qrMatrix)+byteStringToQR input filepath = (bsToImg input) >>= writePng filepath  -- | given a filepath, read the QR code as a string in all lowercase --@@ -63,4 +76,10 @@ readQRStrSec :: FilePath -> FilePath -> IO String readQRStrSec filepath keyfile = do     enc <- (map toLower) . init . (drop 8) . (view _2) <$> readCreateProcessWithExitCode (shell $ "zbarimg " ++ filepath) ""-    (fmap $ liftEither show) . (flip checkSig keyfile) . resolveUpper $ (BS.pack) enc+    (fmap $ liftEither show) . (flip checkSigFile keyfile) . resolveUpper $ (BS.pack) enc++-- | Read an image containing a QR code, decode and verify the signature using the given key.+readQRStrSec' :: FilePath -> (PublicKey, PrivateKey) -> IO String+readQRStrSec' filepath key = do+    enc <- (map toLower) . init . (drop 8) . (view _2) <$> readCreateProcessWithExitCode (shell $ "zbarimg " ++ filepath) ""+    (fmap $ liftEither show) . (flip checkSig key) . resolveUpper $ (BS.pack) enc
src/Data/QRCodes/Image.hs view
@@ -1,21 +1,69 @@ -- | A few functions to deal with the image itself-module Data.QRCodes.Image where+module Data.QRCodes.Image (-- * Functions to convert to JuicyPixels `Image`+                          bsToImg+                          , objToImg+                          -- * Functions to sign and convert to `Image`+                          , bsToImgSec+                          , objToImgSec+                          -- * Functions to sign with user-supplied key and yield an `Image`+                          , bsToImgSec'+                          , objToImgSec'+                          ) where  import Data.Word (Word8) import Codec.Picture.Types as T import Prelude as P import qualified Data.Vector.Storable as V+import qualified Data.ByteString as BS+import Data.ByteString.Lazy (toStrict)+import Data.QRCode+import Data.Aeson+import Data.QRCodes.Utils+import Data.QRCodes.Signature+import Crypto.PubKey.RSA --- | Encode a png, given a matrix+-- | Creates a signed QR code from a strict bytestring and path to keyfile/path where the keyfile should be generated, yielding a JuicyPixels `Image`.+-- Note that QR codes may only contain a small number of characters, so encrypting can sometimes make an object too big to encode.+--+-- > bsToImgSec (BS.pack "hello") ".key.hk"+bsToImgSec :: BS.ByteString -> FilePath -> IO (T.Image Word8)+bsToImgSec string keyfile = bsToImg =<< (((fmap preserveUpper) . (flip mkSigFile keyfile)) string)++-- | Sign a byteString with a given key+--+-- > bsToImgSec' (BS.pack "str") (generate 256 0x10001)+bsToImgSec' :: BS.ByteString -> (PublicKey, PrivateKey) -> IO (T.Image Word8)+bsToImgSec' string key = bsToImg =<< (((fmap preserveUpper) . (flip mkSig key)) string)++-- | Encode an object as a JuicyPixels `Image` with a key in a given file.+objToImgSec :: (ToJSON a) => a -> FilePath -> IO (T.Image Word8)+objToImgSec obj = bsToImgSec (toStrict $ encode obj)++-- | Encode an object as a JuicyPixels `Image` with a key.+objToImgSec' :: (ToJSON a) => a -> (PublicKey, PrivateKey) -> IO (T.Image Word8)+objToImgSec' obj = bsToImgSec' (toStrict $ encode obj)++-- | Create a JuicyPixels `Image` from a `ByteString`+bsToImg :: BS.ByteString -> IO (T.Image Word8)+bsToImg input = do+    smallMatrix <- toMatrix <$> encodeByteString input Nothing QR_ECLEVEL_H QR_MODE_EIGHT False+    let qrMatrix = fattenList 8 $ map (fattenList 8) smallMatrix+    pure $ encodePng qrMatrix++-- | Encode an object as a JuicyPixels `Image`+objToImg :: (ToJSON a) => a -> IO (T.Image Word8)+objToImg obj = let input = toStrict $ encode obj in bsToImg input++-- | Encode a JuicyPixels `Image` given a matrix 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 --- | To help scale the image up+-- | To help scale the image up, e.g.+--+-- > fattenList 8 $ (map fattenList 8) smallMatrix+--+-- to scale @smallMatrix :: [[Word8]]@ by a factor of 8 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
@@ -4,30 +4,45 @@ import Jose.Jws import qualified Data.ByteString.Char8 as BS import Jose.Jwt (unJwt, JwtError)-import Crypto.PubKey.RSA as Cr+import Crypto.PubKey.RSA import Control.Lens import System.Directory import Jose.Jwa (JwsAlg (RS256)) import Data.QRCodes.Utils +-- | Verify a signed token with a key+checkSigFile :: BS.ByteString -> FilePath -> IO (Either JwtError BS.ByteString)+checkSigFile tok filepath = do+    key <- read <$> readFile filepath :: IO (PublicKey, PrivateKey)+    checkSig tok key+ -- | Verify a signed token with a key from a given filepath-checkSig :: BS.ByteString -> FilePath -> IO (Either JwtError BS.ByteString)-checkSig tok filepath = do-    key <- read <$> readFile filepath :: IO (Cr.PublicKey, Cr.PrivateKey)+checkSig :: BS.ByteString -> (PublicKey, PrivateKey) -> IO (Either JwtError BS.ByteString)+checkSig tok key = do     let jws = rsaDecode (view _1 key) tok     return $ fmap (view _2) jws --- | Sign a token (note that we must pass in a processed token since there is no uppercase/lowercase here) with a key from a given filepath-mkSig :: BS.ByteString -> FilePath -> IO BS.ByteString-mkSig string filepath = do+-- | Sign a token. +-- If the key file does not exist, a new key will be generated.+mkSigFile :: BS.ByteString -> FilePath -> IO BS.ByteString+mkSigFile string filepath = do     switch <- doesFileExist filepath     if not switch then do         putStrLn "generating key..."-        key <- Cr.generate 256 0x10001+        key <- generate 256 0x10001         writeFile filepath (show key)     else         return ()-    key' <- read <$> readFile filepath :: IO (Cr.PublicKey, Cr.PrivateKey)-    signedToken <- rsaEncode RS256 (view _2 key') string+    key' <- read <$> readFile filepath :: IO (PublicKey, PrivateKey)+    mkSig string key'++-- | Sign a token with a key.+-- (note that we must pass in a processed token since there is no uppercase/lowercase for QR codes)+--+-- > mkSig (BS.pack "hello") (generate 256 0x10001)+mkSig :: BS.ByteString -> (PublicKey, PrivateKey) -> IO BS.ByteString+mkSig string key = do+    signedToken <- rsaEncode RS256 (view _2 key) string     let signed = fmap unJwt signedToken     liftEither id (return <$> signed)+  
src/Data/QRCodes/Utils.hs view
@@ -4,6 +4,7 @@ import qualified Data.ByteString.Char8 as BS import Data.Char (toLower, toUpper) import Data.List.Utils (replace)+import Data.Word (Word8)  -- | function applied to byteStrings before saving to QR code so that uppercase/lowercase signatures can be preserverd preserveUpper :: BS.ByteString -> BS.ByteString@@ -23,3 +24,6 @@ liftEither :: (Show b, Monad m) => (t -> m a) -> Either b t -> m a liftEither = either (error . show) +swapWord :: Word8 -> Word8+swapWord 1 = 0+swapWord 0 = 1