packages feed

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 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