hs-pkpass 0.3 → 0.4
raw patch · 3 files changed
+156/−29 lines, 3 filesdep +transformersPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
API changes (from Hackage documentation)
+ Passbook: signOpen :: FilePath -> FilePath -> FilePath -> FilePath -> Pass -> IO (FilePath, Text)
+ Passbook: signOpenWithId :: FilePath -> FilePath -> FilePath -> FilePath -> Pass -> Text -> IO FilePath
+ Passbook: signOpenWithModifier :: FilePath -> FilePath -> FilePath -> FilePath -> Pass -> (Text -> Pass -> Pass) -> IO (FilePath, Text)
+ Passbook.Types: Manifest :: [(Text, Text)] -> Manifest
+ Passbook.Types: data Manifest
+ Passbook.Types: instance ToJSON Manifest
Files
- Passbook.hs +143/−27
- Passbook/Types.hs +10/−0
- hs-pkpass.cabal +3/−2
Passbook.hs view
@@ -3,28 +3,22 @@ {-# OPTIONS_GHC -fno-warn-type-defaults #-} -{- |This module provides different functions to sign a 'Pass' using Apple's @signpass@- command-line tool.-- I intend to move the signing process to a Haskell native OpenSSL binding later- on, but due to time constraints didn't get around to it yet.+{- |This module provides different functions to sign a Passbook 'Pass'. - If you want to use this module with an existing .pkpass file, you can import its- @pass.json@ file using the function 'loadPass'.+ /Please read the documentation!/ - The function 'signpass' creates a random UUID during the signing process, uses this UUID- as the passes' serial number and returns it along with the path to the signed pass.+ One set of functions uses the @signpass@ tool included in Apple's Passbook+ Support Materials to sign the pass. This uses the system keychain directly, but+ works on OS X only. - The funciton 'signpassWithModifier' creates a random UUID during the signing process,- passes this UUID on to a function that modifies the pass accordingly (e.g. sets the serial- number or the barcode payload to the UUID) and otherwise works like 'signpass'.- The function 'updateBarcode' is provided as well.+ The other set of functions uses OpenSSL instead, in this case you need to export+ your certificate using the process described in the OpenSSL section of this document. - The function, 'signpassWithId', takes an existing ID and signs the pass- using that for the serial number and file name. This will most likely be used- for updating existing passes.+ If you want to use this module with an existing .pkpass file, you can import it+ using the function 'loadPass'. Please note that you still need to provide the+ assets in a separate directory, 'loadPass' only parses the @pass.json@ file. - Using the function is very simple, assuming you have created a 'Pass' called+ Using these function is very simple, assuming you have created a 'Pass' called @myPass@ and you have the related assets (e.g. the logo.png and icon.png files) stored in a folder named @myPass/@. @@ -36,22 +30,25 @@ You will find the pass at @path@ with the filename @passId.pkpass@. Using the types from "Passbook.Types" ensures that passes are generated correctly. - The @signpass@ utility must have access to your Keychain to be able to retrieve- your Pass Type Certificate for the pass to sign. You should run it once outside- of any program to grant it full access to your Keychain.- Please note that an @icon.png@ file /must be/ present in your asset folder,- otherwise the generated pass will not work.+ otherwise the generated pass will not work. This is /not/ checked by this module. Refer to Apple's Passbook documentation at <https://developer.apple.com/passbook/> for more information or to retrieve the @signpass@ tool which is included in the Passbook Support Materials. (iOS Developer Membership necessary) - This module will most likely only work on OS X machines. -}-module Passbook ( signpass+module Passbook ( -- * Sign using signpass+ -- $signpass+ signpass , signpassWithId , signpassWithModifier+ -- * Sign using OpenSSL+ -- $openssl+ , signOpen+ , signOpenWithModifier+ , signOpenWithId+ -- * Helper functions , genPassId , updateBarcode , loadPass@@ -59,14 +56,17 @@ import Codec.Archive.Zip import Control.Monad (liftM)+import Control.Monad.IO.Class (liftIO) import Data.Aeson-import Data.ByteString.Lazy as LB+import qualified Data.ByteString.Lazy as LB import Data.Conduit import Data.Conduit.Binary hiding (sinkFile) import Data.Conduit.Filesystem import qualified Data.Text as ST-import Data.Text.Lazy as LT+import Data.Text.Lazy (Text)+import qualified Data.Text.Lazy as LT import Data.UUID+import Filesystem.Path (filename) import Filesystem.Path.CurrentOS (encodeString) import Passbook.Types import Prelude hiding (FilePath)@@ -76,9 +76,22 @@ default (LT.Text) +-- $signpass+-- These functions sign a 'Pass' using the @signpass@ tool provided by Apple in the+-- Passbook Support Materials. You can find those at <https://developer.apple.com/passbook/>+-- however, an iOS Developer Membership is necessary for the download.+--+-- The signpass utility needs access to your keychain. OS X will prompt you for this the first+-- time you run the tool.+--+-- Please make sure that the @signpass@ tool is within your $PATH. These functions work on OS X+-- only.+ -- |Takes the filepaths to the folder containing the path assets -- and the output folder, a 'Pass' and uses a random UUID to -- create and sign the pass.+--+-- /Important:/ OS X only! signpass :: FilePath -- ^ Input file path (asset directory) -> FilePath -- ^ Output file path -> Pass -- ^ The pass to sign@@ -92,6 +105,10 @@ -- modifier function that updates the pass with the generated UUID. -- This is useful for cases where you want to store the UUID in the barcode -- or some other field on the pass as well.+--+-- An example function for use with this is 'updateBarcode'.+--+-- /Important:/ OS X only! signpassWithModifier :: FilePath -- ^ Input file path (asset directory) -> FilePath -- ^ Output file path -> Pass -- ^ The pass to sign@@ -103,13 +120,16 @@ return (passPath, passId) -- |Updates the barcode in a pass with the UUID. This can be passed to 'signpassWithModifier'+-- or 'signOpenWithModifier'. updateBarcode :: ST.Text -> Pass -> Pass updateBarcode n p = case barcode p of Nothing -> p -- This pass has no barcode. Just ob -> p { barcode = Just ob { altText = Just n , message = n } } --- |Creates and signs a 'Pass' with an existing ID.+-- |Signs the 'Pass' using the provided ID, no random UUID generation happens here.+--+-- /Important:/ OS X only! signpassWithId :: ST.Text -- ^ The pass ID -> FilePath -- ^ Input file path (asset directory) -> FilePath -- ^ Output file path@@ -123,6 +143,102 @@ signcmd lazyId tmp passOut rm_rf tmp return (passOut </> LT.append lazyId ".pkpass")++-- |Helper function to generate a hash+genHash :: FilePath -> Sh (Text, Text)+genHash file = do+ rawhash <- run "openssl" ["sha1", toTextIgnore file]+ let hash = LT.drop 1 $ LT.dropWhile (/= ' ') rawhash+ return (toTextIgnore $ filename file, LT.filter (/= '\n') hash)++-- |Render JSON and put it in a file+saveJSON :: ToJSON a => a -> FilePath -> IO ()+saveJSON json path = LB.writeFile (LT.unpack $ toTextIgnore path) $ encode json++-- |Helper function to sign the manifest+sslSign :: FilePath -- ^ Certificate+ -> FilePath -- ^ Key+ -> FilePath -- ^ Temporary directory containing manifest.json+ -> Sh Text+sslSign cert key tmp =+ run "openssl" [ "smime", "-binary"+ , "-sign"+ , "-signer", toTextIgnore cert+ , "-inkey" , toTextIgnore key+ , "-in", "manifest.json"+ , "-out", "signature"+ , "-outform", "DER" ]++-- $openssl+-- These functions sign a 'Pass' using OpenSSL. They work on operating systems+-- other than OS X as well. To use these you need to export your certificate+-- from the keychain. Assuming you have saved the certificatea as @cert.p12@+-- , the conversion works like this:+--+-- > $ openssl pkcs12 -in cert.p12 -clcerts -nokeys -out certificate.pem+-- > $ openssl pkcs12 -in cert.p12 -nocerts -out keypw.pem+--+-- Enter a password for your key file, you will only need this once in the next step.+-- Then strip the password from your key file using:+--+-- > $ openssl rsa -in keypw.pem -out key.pem+--+-- /Important:/ All paths passed to these functions /must/ be absolute.+++-- |Takes the filepaths to the folder containing the path assets+-- and the output folder, the paths to the certificate and the key,+-- a 'Pass' and uses a random UUID to create and sign the pass.+signOpen :: FilePath -- ^ Input file path (asset directory)+ -> FilePath -- ^ Output folder+ -> FilePath -- ^ Certificate+ -> FilePath -- ^ Certificate key+ -> Pass -- ^ The pass to sign+ -> IO (FilePath, ST.Text) -- ^ The signed .pkpass file and ID+signOpen passIn passOut cert key pass = do+ passId <- genPassId+ passPath <- signOpenWithId passIn passOut cert key pass passId+ return (passPath, passId)++-- |Works like 'signOpen', except for the fourth argument which is a+-- modifier function that updates the pass with the generated UUID.+-- This is useful for cases where you want to store the UUID in the barcode+-- or some other field on the pass as well.+--+-- An example function for use with this is 'updateBarcode'.+signOpenWithModifier :: FilePath -- ^ Input file path (asset directory)+ -> FilePath -- ^ Output folder+ -> FilePath -- ^ Certificate+ -> FilePath -- ^ Certificate key+ -> Pass -- ^ The pass to sign+ -> (ST.Text -> Pass -> Pass) -- ^ Modifier function+ -> IO (FilePath, ST.Text) -- ^ The signed .pkpass file and ID+signOpenWithModifier passIn passOut cert key pass f = do+ passId <- genPassId+ passPath <- signOpenWithId passIn passOut cert key (f passId pass) passId+ return (passPath, passId)++-- |Signs the 'Pass' using the provided ID, no random UUID generation happens here.+signOpenWithId :: FilePath -- ^ Input file path (asset directory)+ -> FilePath -- ^ Output folder+ -> FilePath -- ^ Certificate+ -> FilePath -- ^ Certificate key+ -> Pass -- ^ The pass to sign+ -> ST.Text -- ^ The pass ID+ -> IO FilePath -- ^ The signed .pkpass file+signOpenWithId passIn passOut cert key pass passId = shelly $ silently $ do+ let tmp = passOut </> passId+ passFile = LT.append (LT.fromStrict $ passId) ".pkpass"+ cp_r passIn tmp+ liftIO $ renderPass (tmp </> "pass.json") (pass { serialNumber = passId })+ cd tmp+ manifest <- liftM Manifest $ pwd >>= ls >>= mapM genHash+ liftIO $ saveJSON manifest (tmp </> "manifest.json")+ sslSign cert key tmp+ files <- liftM (map (toTextIgnore . filename)) $ ls =<< pwd+ run "zip" ((toTextIgnore $ passOut </> passFile) : files)+ rm_rf tmp+ return (passOut </> passFile) -- |Generates a random UUID for a Pass using "Data.UUID" and "System.Random" genPassId :: IO ST.Text
Passbook/Types.hs view
@@ -32,6 +32,7 @@ , NumberStyle(..) , TransitType(..) , WebService(..)+ , Manifest(..) -- * Auxiliary functions , rgb , mkBarcode@@ -47,6 +48,7 @@ import Data.Attoparsec.Text import qualified Data.HashMap.Strict as HM import Data.Text (Text, pack, unpack)+import qualified Data.Text.Lazy as LT import Data.Time import Data.Typeable import System.Locale@@ -193,6 +195,14 @@ , passContent :: PassType -- ^ The kind of pass and the passes' fields (required) } deriving (Eq, Ord, Show, Read, Typeable)++-- |The manifest.json file+data Manifest = Manifest [(LT.Text, LT.Text)] -- ^ (Filename, Hash)++instance ToJSON Manifest where+ toJSON (Manifest files) =+ let pairs = map (\(f, h) -> LT.toStrict f .= h) files+ in object pairs -- * JSON instances
hs-pkpass.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: hs-pkpass-version: 0.3+version: 0.4 synopsis: A library for Passbook pass creation & signing description: A Haskell library for type-safe creation of Passbook passes and signing through Apple's signpass tool. homepage: https://github.com/tazjin/hs-pkpass@@ -38,4 +38,5 @@ unordered-containers >= 0.2, zip-archive >= 0.1.1.8, system-filepath < 0.5,- bytestring+ bytestring,+ transformers >= 0.3