Saturday, February 9, 2013

Sign your PassBook Passes with OpenSSL!!


I've been trying recently to integrate Apple's Passbook to some of my iOS apps. Passbook is a service introduced by Apple in iOS 6. It works much like a pocket wallet in which customers can put their airport or theater tickets, loyalty cards, coupons and many more. It allows customers to undertake action in the real world by presenting the electronic versions of their loyalty cards, coupons, tickets, etc... to the merchant in store. This is part of Apple's strategy in promoting and fostering mobile payments which still can not find a place in the heart of customers.

Apple has defined the format of Passbook passes and made them as generic as possible in order to give the liberty for businesses to customize the design and the information included in the pass depending on their needs. Passbook also comes with some nice features like the geolocated alerts that are displayed in the lock screen when a user is around a shop or a place where the pass can be redeemed. Unlocking the screen will then show Passbook and invite the customer to use the pass. Passes do also require the URL of a web service allowing business to remotely update them. Apple also made sure passes are provisioned and distributed easily, either by email, push notifications, iCloud or in-app by connecting to the remote web service that manages them.

When it comes to security, Passbook passes are zip archive packages containing a bunch of data files (pass resources) and a PKCS#7 signature, much known as S/MIME, generated by signing a manifest file using a certificate provided by Apple. To get the certificate, developers need of course to subscribe to one of Apple's developer programs. Signing passes is necessary to be able to discard those signed by unauthorized issuers.

Apple provides a sample OS/X app named "signpass" to sign passes on Mac. The same code however can not be utilized on iOS because the Cryptographic Message Syntax API does not seem to be implemented yet. Technically, passes shall not be signed on iOS for security reasons, because this requires the signing certificate to be loaded into the application. Even it is technically possible to securely provision the app with the certificate, assuming that it has been downloaded from a server using SSL and securely stored in the device using the iOS keychain service, this should be avoided as much as possible because the deployment of passes should be centralized.

Personally I had to do this on iOS in order to not be dependent on a specific server, especially when presenting demos. It is not always possible in this kind of situations to have access to the remote server. I searched around on the internet and could not find a sample code that shows how to generate S/MIME signature on iOS, while many people were suggesting using OpenSSL. I had then no choice but to use OpenSSL.

The OpenSSL library can be built on iOS. Here you can find a script to build it with the latests iOS SDK => https://github.com/x2on/OpenSSL-for-iPhone . You'll probably have to slightly tweak the script by setting the adequate SDK version.

Once OpenSSL built, you can link it with your iOS app and use the following code to generate the PKCS#7 signature.



 #include <openssl/pem.h>  
 #include <openssl/pkcs12.h>  
 #include <openssl/pkcs7.h>  
 #include <openssl/err.h>

+(BOOL)signManifest:(NSData *)manifest toPath:(NSString *)signaturePath withPKCS12FilePath:(NSString *)pkcs12 andAdditionalCACertPath:(NSString *)intermediateCertPath {  
      NSLog(@"%s", __FUNCTION__);  
      BOOL result = NO;  
      FILE *fp;  
      BIO *in = NULL, *out = NULL;  
      PKCS12 *p12;  
      X509 *scert = NULL, *caCert = NULL;  
      STACK_OF(X509) *ca = NULL;  
      EVP_PKEY *skey = NULL;  
      PKCS7 *p7 = NULL;  

      /* For simple S/MIME signing use PKCS7_DETACHED.  
       * On OpenSSL 0.9.9 only:  
       * for streaming detached set PKCS7_DETACHED|PKCS7_STREAM  
       * for streaming non-detached set PKCS7_STREAM  
       */  
      int flags = PKCS7_DETACHED | PKCS7_BINARY;  
      OpenSSL_add_all_algorithms();  
      ERR_load_crypto_strings();

      /* Read in signer certificate and private key */  
      if (!(fp = fopen([pkcs12 UTF8String], "rb"))) {  
           NSLog(@"%s Error opening file %@", __FUNCTION__, pkcs12);  
           goto end;  
      }  
      p12 = d2i_PKCS12_fp(fp, NULL);  
      fclose (fp);  
      if (!p12) {  
        NSLog(@"%s Error reading PKCS#12 file", __FUNCTION__);  
        ERR_print_errors_fp(stderr);  
        goto end;  
      }
  
      if (!PKCS12_parse(p12, "", &skey, &scert, &ca)) {  
        NSLog(@"%s Error parsing PKCS#12 file", __FUNCTION__);  
        ERR_print_errors_fp(stderr);  
        goto end;  
      }  
      PKCS12_free(p12);  
         if (!scert || !skey)  
              goto end;  

   /* Read intermediate CA root certificates */  
   if (!(fp = fopen([intermediateCertPath UTF8String], "rb"))) {  
           NSLog(@"%s Error opening file %@", __FUNCTION__, intermediateCertPath);  
     goto end;  
   }  
   caCert = d2i_X509_fp(fp, NULL);  
   fclose (fp);  
   if (!caCert) {  
     NSLog(@"%s Error reading X509 Certificate file", __FUNCTION__);  
     ERR_print_errors_fp(stderr);  
     goto end;  
   }

   //Add the intermediate CA certificate to the signing stack  
   if (ca == NULL) {  
     ca = sk_X509_new_null();  
   }  
   sk_X509_push(ca, caCert);

      /* Open content being signed */  
      in = BIO_new_mem_buf((void *)[manifest bytes], [manifest length]);  
      if (!in)  
           goto end;

      /* Sign content */  
      p7 = PKCS7_sign(scert, skey, ca, in, flags);  
      if (!p7)  
           goto end;  
     
      //create a file handle to where the signature will be saved
      out = BIO_new_file([signaturePath UTF8String], "w");  
      if (!out)  
           goto end;  
      //if (!(flags & PKCS7_STREAM))  
      //     BIO_reset(in);  
      /* Write out S/MIME message */  
      if (!i2d_PKCS7_bio(out, p7))  
           goto end;

      result = YES;  
 end:  
      if (result == NO)  
   {  
           NSLog(@"%s Error Signing Data", __FUNCTION__);  
           ERR_print_errors_fp(stderr);  
   }  
   if (ca) {  
     sk_X509_free(ca);  
   }  
      if (p7)  
           PKCS7_free(p7);  
      if (scert)  
           X509_free(scert);  
      if (skey)  
           EVP_PKEY_free(skey);  
      if (in)  
           BIO_free(in);  
      if (out)  
           BIO_free(out);  
   return result;  
 }  

This is quite pure C code, except some text string and buffers that have been wrapped with some Objective-C classes NSString & NSData. Those can easily be replaced with char* arrays and you get a pure C code.

Hope this helps :-)