Extracting a PKCS7 Container for Receipt Validation with Swift

So you’ve prepared to test receipt validation by setting up your app in iTunes Connect.

You’ve brought in a cryptography library like OpenSSL to be able to work with the PKCS #7 container that acts as the “envelope” for the receipt. Perhaps you’ve even done it the “easy way” with CocoaPods.

You’ve located and loaded the receipt for validation.

Now you’re ready to extract the PKCS #7 container and work with it.

The aim of this guide is to get you started using the OpenSSL library in your Swift code by employing it to extract the receipt contents from its PKCS #7 container.

Just want the code? Here you go!

Recap from the previous guide

In Loading a Receipt for Validation with Swift, I began the process of breaking out the various steps of the receipt validation process into separate single-responsibility structs with clearly named functions to help clarify what each piece of code is doing.

Recall that I’ve created a main Type called ReceiptValidator, with references to several smaller single-responsibility Types that it uses to accomplish the overall validation process. So accordingly, as of my last post in the series, I’ve created a ReceiptLoader that finds the receipt on the file system and loads it into memory.

If a validation step ever fails along the way, I’ve decided to take advantage of Swift’s error throwing features to clearly describe what failed. So far, there’s only one case:

1
2
3
enum ReceiptValidationError : Error {
    case couldNotFindReceipt
}

I’ll expand this enum Type to cover more failure conditions in this guide.

ReceiptExtractor struct outline

The OpenSSL library comes to us in the form of a C static library. It’s not a beautiful API to say the least. The names of the Types and functions are really cryptic at times, so I’ve decided it’s best for my own memory to wrap each step in small function routines that are named for what they do.

So supposing you’ve located and loaded the receipt data, or used Store Kit to request a receipt from Apple… Take a look at this new ReceiptExtractor skeleton of a struct to get an idea of what’s going to be required to extract the PKCS7 container for the receipt:

1
2
3
4
5
6
struct ReceiptExtractor {
    func extractPKCS7Container(_ receiptData: Data) throws -> UnsafeMutablePointer<PKCS7> {
        // use Open SSL to extract the PKCS7 container
        // throw a ReceiptValidationError if something goes wrong in this process
    }
}

New ReceiptValidationError cases

When extracting the receipt information from the PKCS7 container, there are going to be things that would cause overall validation to fail. For example, if the receipt Data instance ends up being empty, that’s a validation failure. The PKCS7 container needs to have information inside of it for validation to pass (obviously).

So in this guide, I’ll expand the ReceiptValidationError enum to have the following cases:

1
2
3
4
enum ReceiptValidationError : Error {
    case couldNotFindReceipt
    case emptyReceiptContents
}

Preparation step: PKCS7 union accessors

Before attempting to work with OpenSSL’s PKCS7 functions, you’ve got to do a little prep work to get the functions to play nicely with Swift.

Unfortunately, Swift doesn’t work well with C union types. It simply can’t see things defined with a C union.

Thankfully, we can work around the problem by creating some wrappers. If you’ll add two new files to your project and implement them, you’ll be on your way. They are:

  • pkcs7_union_accessors.h
  • pkcs7_union_accessors.c

pkcs7_union_accessors.h implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#ifndef pkcs7_union_accessors_h
#define pkcs7_union_accessors_h

#include <openssl/pkcs7.h>

char *pkcs7_d_char(PKCS7 *ptr);
ASN1_OCTET_STRING *pkcs7_d_data(PKCS7 *ptr);
PKCS7_SIGNED *pkcs7_d_sign(PKCS7 *ptr);
PKCS7_ENVELOPE *pkcs7_d_enveloped(PKCS7 *ptr);
PKCS7_SIGN_ENVELOPE *pkcs7_d_signed_and_enveloped(PKCS7 *ptr);
PKCS7_DIGEST *pkcs7_d_digest(PKCS7 *ptr);
PKCS7_ENCRYPT *pkcs7_d_encrypted(PKCS7 *ptr);
ASN1_TYPE *pkcs7_d_other(PKCS7 *ptr);

#endif /* pkcs7_union_accessors_h */

pkcs7_union_accessors.c implementation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "pkcs7_union_accessors.h"

inline char *pkcs7_d_char(PKCS7 *ptr) {
    return ptr->d.ptr;
}

inline ASN1_OCTET_STRING *pkcs7_d_data(PKCS7 *ptr) {
    return ptr->d.data;
}

inline PKCS7_SIGNED *pkcs7_d_sign(PKCS7 *ptr) {
    return ptr->d.sign;
}

inline PKCS7_ENVELOPE *pkcs7_d_enveloped(PKCS7 *ptr) {
    return ptr->d.enveloped;
}

inline PKCS7_SIGN_ENVELOPE *pkcs7_d_signed_and_enveloped(PKCS7 *ptr) {
    return ptr->d.signed_and_enveloped;
}

inline PKCS7_DIGEST *pkcs7_d_digest(PKCS7 *ptr) {
    return ptr->d.digest;
}

inline PKCS7_ENCRYPT *pkcs7_d_encrypted(PKCS7 *ptr) {
    return ptr->d.encrypted;
}

inline ASN1_TYPE *pkcs7_d_other(PKCS7 *ptr) {
    return ptr->d.other;
}

Bridging header updates

After you create the union accessor files, you need to update your project’s bridging header to import the new header file:

1
2
3
#import <openssl/pkcs7.h>
#import <openssl/objects.h>
#import "pkcs7_union_accessors.h"

ReceiptExtractor struct implementation

Now it’s time to dive into the actual implementation of what I’m calling a ReceiptExtractor. Have a look at the code with some explanatory comments following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
struct ReceiptExtractor {
    func extractPKCS7Container(_ receiptData: Data) throws -> UnsafeMutablePointer<PKCS7> {
        let receiptBIO = BIO_new(BIO_s_mem())       
        BIO_write(receiptBIO, (receiptData as NSData).bytes, Int32(receiptData.count))
        let receiptPKCS7Container = d2i_PKCS7_bio(receiptBIO, nil)
        
        guard receiptPKCS7Container != nil else {
            throw ReceiptValidationError.emptyReceiptContents
        }
        
        let pkcs7DataTypeCode = OBJ_obj2nid(pkcs7_d_sign(receiptPKCS7Container).pointee.contents.pointee.type)
        
        guard pkcs7DataTypeCode == NID_pkcs7_data else {
            throw ReceiptValidationError.emptyReceiptContents
        }
        
        return receiptPKCS7Container!
    }
}

ReceiptExtractor struct explanation

Most of the code above is a Swift translation of what’s found at Objc.io’s Receipt Validation guide.

I did a little research over at the OpenSSL site though, and thought it might be helpful for the curious to know what some of these non-intuitive function names stand for and what they do.

BIO_new for example. “BIO” stands for “Basic I/O”. It’s an abstraction over the underlying basic input and output operations that your app uses for cryptographic operations.

What we’re doing with BIO_new(BIO_s_mem()) is saying that we want a new Basic I/O mechanism that uses memory for its I/O operations.

BIO_write takes the receiptData that was located and loaded, and writes the entire length of its bytes to memory (the receiptBIO that was created first).

To get the actual PKCS #7 container, the d2i_PKCS7_bio function is used.

Once we have the container in hand, it’s a matter of making sure it has contents.

I couldn’t find a lot of information about the call to pkcs7_d_sign, but the primary point of line 13 above is to get a “numerical identifier”, which is what “NID” stands for in OBJ_obj2nid.

Digging into the PKCS #7 container, you can access the right property and convert it to a numerical identifier that you can check.

As long as the NID returned is equal to the NID_pkcs7_data constant value, things are good. If they’re not, that means the receipt has no information and validation fails (thus, the guard and throw statement in lines 14-15).

If everything passes the guard, though, the PKCS #7 container is returned, and we’re ready for the next step of the receipt validation process, which is to verify the signature on the receipt with Apple’s root certificate. That, however, will happen in another entry to this series.

Until next time!

comments powered by Disqus