Blog Stats
  • Posts - 150
  • Articles - 0
  • Comments - 36
  • Trackbacks - 0

 

Part 3–PassBook Server

So far we have covered some basic principals, a simple database schema to store pass-jobs.   Next step is to go about actually tie all this together and generate some passes.

I’m being careful to avoid talking specifics of how Apple Passbook works as this is still subject to NDA.   However I can point out that I have worked out all principals using pass generators in the public domain,  i.e

http://passsource.com/

http://passk.it/

Using these websites you can download sample boarding passes, tickets and coupons and rename the .pkpass files back to .zip.    So without breaking any of the terms of the NDA I have reverse engineered these samples to produce this guide.

Insider the zip you have the following base elements -

icon.png
icon@2x.png
logo.png
logo@2x.png
manifest.json
pass.json
singature

 

By playing around with passk.it and passsource, you can see what the icons and image files are.   The meat of actually what happens, is in manifest.json,  pass.json and signature

Manifest.json looks like this -

{
    "pass.json" : "de440478cd0db57d35474d88f455e0bcdd0d3864" 
,    "icon.png" : "ba47a8021c8d74d2146d7244c8a0566be37df43b" 
,    "icon@2x.png" : "bd5442b4b08aa4dde333ec9ef0269e7fd93140b3" 
,    "logo.png" : "780540b3a324bf66aeaee2d352283371356e9502" 
,    "logo@2x.png" : "a718ffd4e611e404dd3eb701454bcaefdabbe311" 
}

Its a JSON dictionary of all the files that make up you’re pass excluding the manifest.json itself and signature.

The long hex number alongside is the SHA1 hash of the bytes of each file.  In C#  I wrote a bit of code to loop through all files and generate these hash’s as following -

 private string generatehash(string filePathAndName)
        {
            byte[] fileData = File.ReadAllBytes(filePathAndName);
            return generatehash(fileData);
        }

        private string generatehash(byte[] fileData)
        {
            string hashText = "";
            string hexValue = "";

            byte[] hashData = SHA1.Create().ComputeHash(fileData); // SHA1 or MD5

            foreach (byte b in hashData)
            {
                hexValue = b.ToString("X").ToLower(); // Lowercase for compatibility on case-sensitive systems
                hashText += (hexValue.Length == 1 ? "0" : "") + hexValue;
            }

            return hashText;
        }

A little bit of string handling is required to build the manifest.json file up.   But once you’ve looked at File.IO at getting a list of files in a given directory its plain sailing using the algorithm above.

The lion share of the work goes on in pass.json,   this file describes exactly what’s on a pass.  A sample of this, is shown below -

{
    "relevantDate" : "2012-07-28T04:10Z"
    ,"locations":[
        {"longitude":-122.3748889, "latitude":37.6189722}
    ]
    ,"barcode" : {
        "message" : "barcode"
        ,"format" : "PKBarcodeFormatPDF417"
        ,"messageEncoding" : "iso-8859-1"
        ,"altText" : "alt barcode"
    }
    
    ,"logoText" : "Skyport Airways","foregroundColor" : "rgb(22, 55, 110)","backgroundColor" : "rgb(50, 91, 185)"


    ,"boardingPass":
    {
         "transitType" : "PKTransitTypeAir"
        ,"headerFields":[{"key":"gate","label":"GATE","value":"23","changeMessage":"Gate changed to %@."}]
        ,"secondaryFields":[{"key":"passenger","label":"PASSENGER","value":"Mike H","changeMessage":"Passenger changed to %@."}]
        ,"auxiliaryFields":[{"key":"depart","label":"SAN FRANCISCO","value":"SFO","changeMessage":"Origin changed to %@."},{"key":"boardingTime","label":"DEPART","value":"2:25","changeMessage":"Boarding time changed to %@."},{"key":"flightNewName","label":"FLIGHT","value":"815","changeMessage":"Flight number changed to %@"},{"key":"class","label":"DESIG.","value":"Coach"},{"key":"date","label":"DATE","value":"7\/22"}]
        ,"backFields":[{"key":"passport","label":"PASSPORT","value":"Canadian\/Canadien"},{"key":"residence","label":"RESIDENCE","value":"5780 E Mission St, San Jose, CA"},{"key":"terms","label":"TERMS","value":"stuff"}]
    }


    ,"organizationName" : "ORGNAM"
    ,"serialNumber" : "1234"
    ,"formatVersion": 1
    ,"passTypeIdentifier" : "pass.com.binaryrefinery.www.boarding"
    ,"webServiceURL" : "http://192.168.0.1/passserver/"
    ,"authenticationToken" : "D9BB5A2D-9BDA-4E01-9480-604F235BCDFF"
    ,"teamIdentifier" : "TEAMID"
    ,"description" : "Boarding Pass"
}

So in article 1, I showed what my template file does.    My code substitutes values from a table of database fields into this file.   I have a straight table for writing in individual field value like the barcode value i.e  $$Message,  + others for substituting fields into the JSON dictionaries for headerFields, secondaryFields etc.

See article 2, for details of all the tables I am using.

Security is implemented by a couple of means.    The organizationName, passTypeIdentifier and teamIdentifier tie the pass to a given provider as verified by Apple.

To get these you will have to signup for an Apple Registered iOS developer account (about £60/$99 PA).   Worth doing, and give you all the NDA details that I can’t yet discuss.

The Organisation Name, is tied to you too,  so companies that generate passes for lots of organisations will need to create a developer account for each they represent;  or use a blanket one to cover them all.

The webServiceURL/Authentication Token is how you are able to update the pass once deployed.   This is strictly NDA.  So we will cover this in future articles.

Once you’ve built you’re pass.json.      That too needs to be included in your manifest.json file along with its SHA1 hash.

The next piece of the jigsaw it to produce a signed version of the manifest.json file which lives in a file called signature.

To sign the file,  you will need to generate a pass signing cert.   Once again this is done through the Apple Developer website as a registered developer.   I exported this signature from my Mac that generated the certificate as a p12 file and exported it to my Windows PC that I’m using for this project (then imported it).

I also needed on my Windows machine to install a trusted root certificate from Apple.

I got the ‘World Wide Developer Relations’ cert from -

http://www.apple.com/certificateauthority/

So in code the first thing I needed to-do is pull the correct cert out of the certificate store on my windows machine.  Code to achieve this shown below -

private X509Certificate2 getServerCert(string nameofpass)
        {
            // Open the cert store on the Local Machine
            X509Store store = new X509Store(StoreLocation.CurrentUser);

            if (store != null)
            {
                // Store exists, so open it and search through the certs for the Apple cert


                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection certs = store.Certificates;

                if (certs.Count > 0)
                {

                    for (int i = 0; i < certs.Count; i++)
                    {

                        X509Certificate2 cert = certs[i];
                        Console.WriteLine(cert.Subject);
                        if (cert.Subject.ToLower().Contains(nameofpass)) // this finds the relevant cert
                        {
                            // Cert found, so return it
                            return certs[i];
                        }
                    }
                }
            }
            return null;
        }

This bit of code does the trick,  matching a pass’s name to that stored in the machine (in my case users) certificate store. 

Next bit was to use this cert to sign the manifest file. I pass in the contents of the manifest file as a string (I do it that way, because I want to-do as much stuff in memory) -

private byte[] signit(string manifest)
        {
            byte[] manifestbytes = ASCIIEncoding.ASCII.GetBytes(manifest);

            ContentInfo contentinfo = new ContentInfo(manifestbytes);
            SignedCms signedcms = new SignedCms(contentinfo, true);

               CmsSigner oursigner = new CmsSigner(ourcert);
            signedcms.ComputeSignature(oursigner);
            return signedcms.Encode();
        }

 

So there we are,  a signed and populated pass.   Final stages are to ZIP the contents to produce a single .pkpass file.

I used the Sharp2Zip .net library.  This provides all that you need to construct the zip.

For details -

http://www.icsharpcode.net/opensource/sharpziplib/

 

The final element is deploying this to your users.   Obviously this may vary depending on your application.  For my simple needs I wanted to deploy my passes view a web-server.  However you can deploy from email too.

I created a very simple pass.htm containing a link to my pass,  that looks as follows -

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
</head>
<body>
<a href="boarding.pkpass">Get You're Pass</a>
</body>

</html>

So obviously this would need to have protection in code around it, to restrict which users could access which passes.

I copied my pass.htm and boarding.pkpass onto a web-server (note this works great on Azure too).    You need to add the pkpass mime type to your web server.    This is

.pkpass AS application/vnd.apple.pkpass

Net result hit the web-page from an iOS 6 Simulator (again you need that Apple developers account to get this).

Click the link and the pass installs.

Quite a few steps, but you’ve only got to go through them once.


Feedback

No comments posted yet.


Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 

 

 

Copyright © Richard Jones