qr-imager 0.2.0.0 → 0.2.1.0
raw patch · 6 files changed
+122/−36 lines, 6 files
Files
- README.md +5/−5
- qr-imager.cabal +4/−4
- src/Data/QRCodes.hs +29/−10
- src/Data/QRCodes/Image.hs +55/−7
- src/Data/QRCodes/Signature.hs +25/−10
- src/Data/QRCodes/Utils.hs +4/−0
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