Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-05-31

Using NSUndoManager from Swift

Until Apple releases an undo manager that can accept closures, we will need to use the present NSUndoManager to implement the undo/redo functionality that all users expect. When I tried to use NSUndoManager, I ran into a few problems. Not unsurmountable, but using NSUndoManager is not as simple as some other Cocoa/Foundation classes.

Here are my experiences:

1) The registerUndoWithTarget function pretty much works as advertised. But the selector that is called must be marked with "@objc". That would be fine, but sometimes I want to pass an parameter type that is not known to Objective-C, which won't work because all functions marked with "@objc" must use types that are known to Objective-C. Besides, the parameter must be an object, this means no struct's or enum's.
These two problems caused me to implement a generic Wrapper class and define the @objc func as accepting AnyObject. Example:

class Wrapper<T> {
    var payload: T
    init(_ data: T) {
        self.payload = data
    }

}

enum Action {
    case SET_STRING(String),
}

extension MyClass {
       
    func dispatch(action: Action) -> ActionResult {
        
        let oldString = string
        
        let result = doAction(action)
        
        if result == .CHANGED {
            if let undoManager = g_undoManager {
                undoManager.registerUndoWithTarget(self, selector: "dispatchFromWrapper:", object: Wrapper(Action.SET_STRING(oldString)))
            }
        }
        
        return result
    }
    
    @objc func dispatchFromWrapper(wrapper: AnyObject) {
        dispatch((wrapper as! Wrapper<Action>).payload)
    }


Note 1: When instantiating a Wrapper object, we can use the abbreviated form: Wrapper(Action.SET_STRING(oldString)) instead of the full form: Wrapper<Action>(Action.SET_STRING(oldString)) thanks to type inference.

Note 2: The MyClass definition contains a var called "string" of the type "String".

Don't let the g_undoManager throw you for a loop: each application (normally) uses only one NSUndoManager. The NSUndoManager is part of every NSWindow and NSView. To gain global access to the undoManager I used the following code in the AppDelegate:

// Make the undo manager global

var g_undoManager: NSUndoManager?

class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        

        g_undoManager = window.undoManager
...

Perhaps not the best design possible, but it works!

PS: for more on the dispatch pattern that I used here, see: Swift "Dispatch" Design Pattern.

2) The prepareWithInvocationTarget does not seem usable. I tried several variations, including introducing a category on NSUndoManager, but even though some variations were compilable the app always fails at runtime with an "unrecognized selector sent to" error.

Hopefully Apple will bring out a closure oriented approach soon.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-29

Update: String extensions

The post that contains String extensions has been updated.

It now includes the new functions:

    /// Returns true if the character at the given index is a lowercase character as defined in the lowercase character set. Works for UTF16 characters only.
    
    func isLowercaseCharacterAtIndex(index: String.Index) -> Bool {...}
    
    
    /// Returns true if the character at the given index is an uppercase character as defined in the lowercase character set. Works for UTF16 characters only.

    func isUppercaseCharacterAtIndex(index: String.Index) -> Bool {...}

    
    /// Replaces the character at the specified index with the given character. Returns true if the string was changed, false if not.
    
    mutating func replaceCharacterAtIndex(index: String.Index, with char: Character) -> Bool {...}
    
    
    /// Changes the case of the character at the specified index if the character is a member of either the lowercase character set or the uppercase character set. Returns true if the string was changed, false if not.
    

    mutating func changeCaseOfCharacterAtIndex(index: String.Index) -> Bool {...}


Note that while these functions only work for UTF16 characters, the String they work on may contain  other characters as well.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-28

Mouse up, mouse down and popup menu's

A quick note today: I found out the hard way that after a popup menu is dismissed by clicking outside of the menu, but inside the associated view, the mouseUp function is called without a preceding call to mouseDown.

My solution is to protect the mouseUp handler with a test for the mouseDown position. If the mouseDown position is 'nil', then the mouseUp will simply exit. The mouseDown position is of course initialised in mouseDown and removed in mouseUp.

Like this:
    
    var mouseDownPosition: CGPoint?

    
    override func mouseDown(theEvent: NSEvent) {
        
        if mouseDownPosition != nil {
            log.atLevelInfo(id: 0, source: "mouseDown", message: "Old mouseDownPosition still available, overwriting now")
        }
                        
        mouseDownPosition = convertPoint(theEvent.locationInWindow, fromView: nil)        
    }

    override func mouseUp(theEvent: NSEvent) {
        
        if mouseDownPosition == nil {
            log.atLevelDebug(id: 0, source: "mouseUp", message: "Exit, no mouseDownPosition available")
            return
        }
        
        ... Handle the mouseUp here
         
        
        // The mouseDownPosition has been consumed, remove it
        
        mouseDownPosition = nil
    }

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-27

Swift "Dispatch" Design Pattern

The parameter loaded enum in Swift makes an interesting pattern possible, I call this the "dispatch" pattern. The dispatch pattern converts a class interface from a function driven design to a data driven design. I.e. the dispatch pattern replaces a lot of API function calls with a single API function call.  The complexity is shifted from "selecting the right function" to "selecting the right parameter".

The signature of the dispatch function is thus:

func dispatch(action: Action) -> Result {}

In other languages (including Objective-C) this pattern is only possible by giving up on static type safety and using "id" or "NSObject" for the type of "Action" and "Result". Type safety is then a dynamic burden on the runtime. The biggest disadvantage from this is that you won't know until program execution if your code is correct.

Swift's parameterised Enum does away with the dynamic checking: it is now possible to determine at compile time if the code is correct. This in turn allows us to use the other advantages of the dispatch pattern:
  • Separating the user interface from the maintenance interface. I.e. the initialisation, setup, configuration and other management calls are clearly separated from the primary functionality the class/object offers to the rest of the application.
  • It becomes much easier to implement undo/redo functionality.
  • The scriptability of the application improves.
  • The API of a class is reduced, as a result it becomes easier to read, use and maintain.
  • A dispatcher is an excellent place to make your classes thread safe.
  • This is of course personal, but I find that the resulting code is much easier to read.
An example, suppose we have a text editing app, and want to create a line on which we can execute editing functions. Without a dispatcher the class would look like:

class Line {
    
    var string: String = ""
    
    func insert(str: String) -> Bool { ... }
    
    func makeUppercase() -> Bool { ... }
    
    func copySelection() -> String { ... }
}

So far so good, but as we add more functionality the interface becomes unwieldy, and what is more important: it becomes ever more difficult to add something like undo/redo as we add more API's to the class.

With a dispatcher function the class (and supporting enums) look as follows:

enum Action {
    case UPPERCASE
    case INSERT(String)
    case COPY
}

enum Result {
    case UNCHANGED
    case CHANGED
    case COPY_RESULT(String)
}

class Line {
    
    var string: String = ""
    
    func dispatch(action: Action) -> Result {
        
        switch action {
        case .UPPERCASE: return makeUppercase()
        case let .INSERT(str): return insert(str)
        case .COPY: return copySelection()
        }
        
    }
    
    private func insert(str: String) -> Bool { ... }
    
    private func makeUppercase() -> Bool { ... }
    
    private func copySelection() -> String { ... }
}

The functions that do the real work are now private and are only called from the dispatcher.
It may be obvious that it is now much easier to add undo/redo functionality since we can implement that by updating the dispatch function and adding the undo and redo functions. We do not need to change any of the functions that implement the core functionality.
Notice how the parameterised Enum in Swift makes this all possible without using "Any" or "AnyObject".

If we were to add XML or JSON conversions to the Action enum, it would also become very easy to add macro recording. 

When using this pattern, make sure that all functions that update the internal data structure are routed through the dispatcher. Functions that return information from the internal data structure -without updating the internal data- do not need to be routed through the dispatcher. But there is also no reason not to use the dispatcher for that!

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-25

Swift "Enum Compare" Design Pattern

Comparing enum's in Swift is easy... if the enum does not have associated values. As in the following two examples:

Example 1

enum Enum1: Int {
    case ONE = 1
    case TWO = 2
}

let a1 = Enum1.ONE
let b1 = Enum1.TWO

if a1 == b1 {
    println("Equal")
} else {
    println("Not equal")
}

Example 2

enum Enum2 {
    case ONE
    case TWO
}

let a2 = Enum2.ONE
let b2 = Enum2.TWO

if a2 == b2 {
    println("Equal")
} else {
    println("Not equal")
}

But if the enum has an associated value things get more difficult:

Example 3

enum Enum3 {
    case ONE
    case TWO(String)
}

let a3 = Enum3.ONE
let b3 = Enum3.TWO("wow")

if a3 == b3 {
    println("Equal")
} else {
    println("Not equal")
}

Will cause the following error: Binary operator '==' cannot be applied to two Enum3 operands

So how can we compare enums with an associated value?
The following pattern does that. I am not claiming that it is the only way, but so far its the best I can come up with. If you have something better, please let us know.

If we add the following function to the Enum3 example, everything compiles fine:
(Thanks to ibex10 for pointing out an error in the original pattern, this has now been fixed)

func == (left: Enum3, right: Enum3) -> Bool {
    
    switch left {
        
    case .ONE:
        
        switch right {
        case .ONE: return true
        default: return false
        }
        
    case let .TWO(str1):
        
        switch right {
        case let .TWO(str2) : return (str1 == str2)
        default: return false // Cover all cases
        }
    }

}

As you can see, for the associated values we need to hand craft the comparison.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-23

OSX Receipt validation in Swift, part 9: Thoughts about obfuscation

This post is about some thoughts I had about obfuscation. Since I am using Receigen to do the heavy lifting I have not tried any of this.

The purpose of obfuscation is to make it as difficult as possible for a hacker to break you code. How can we achieve that?
You may think that, once compiled, it becomes very difficult to "read" the code. But in modern languages (like Swift) there is a great deal of information left in the compiled code that can be used to make it easier for a hacker to find the places where receipt validation is done. But even in the older languages it can be easy to identify those places where calls are made to verify receipts.

I remember a case (about 20 years ago, pre internet!) where I had to use an app that used a "dongle" to prevent illegal copies. Since I used the software in two different buildings I had to take the dongle along with me when I changed locations. Of course I would sometimes forget the dongle. Very frustrating. One day I had enough of this and sat down to take a look at the assembly code. It took about 10 minutes to figure out where the calls were made to the subroutines that checked for the dongle, and replace those calls with NOPs (No OPeration instructions). Presto, the dongle was no longer needed.

With the Swift code from earlier in this series, I estimate that it would take me only slightly longer to hack the code and avoid the validation altogether. The main like of attack would be to start the app in a debugger and see where it would quit. Then retrace the steps back (probably only a few instructions) and see which check would cause the exit. Replace that check with NOPs and try again. Do this a couple of times and eventually you will end up with a cracked version of the application.

But then, I am not a hacker so I would basically take the laborious route. Today's hackers are much more adapt than me at breaking code. They probably have scripts and other tools to do the heavy lifting for them. I think that the Swift code as presented could be cracked by a simple tool, without the need for a hacker to "make his hands dirty".

For example: in the code I use the Apple Root Certificate (ARC) as a plain file. If I wanted to write a tool to automatically hack app, I would scan the app for the root certificate and replace it with a root certificate of my own making. Next the tool would create a new receipt sign it with the fake root certificate and replace the receipt in the app bundle as well.

Renaming the root certificate is good, but does not really help. Since the hacker knows the byte sequence of the ARC he would simply scan for the byte sequence. Even embedding the byte sequence in code does not protect against a sequence scanner. The best option is to encrypt the ARC with a simple encryption scheme of your own making and include the encrypted ARC as a hex dump in the code.

The same approach should be taken for all the strings used in the validation. Encrypt all of them.

A more difficult aspect is the start of the application. Any validation that ends before the start of the application is by definition vulnerable. If the hacker can find the true application start, then he/she can simply skip all validation code and jump directly to the start of the application. The application start should thus be hidden as good as possible. For example by hiding it among a list of other calls and selecting a call from the list dynamically.

Even better would it be to repeat the validation in other parts of your application. Or to add markers to the validation that other parts in your app would check for. For example, calculate a dynamic value before the validation of the root certificate, during the validation of the root certificate you modify that value and store the result in another variable, then in your main app (for example in the run loop) you would check if the values of the two variables are as expected. This way it would be ensured that the root certificate validation was indeed run. A hacker that would find a way to start the app without doing the validation would then fail. If you do something like this in 50 different places, then a hacker would need to do some real work to break your app. He will succeed eventually, but there is no reason to make his life easy ;-) (Though it has to be said that the satisfaction of a hacker in breaking your app goes up with the difficulty of breaking your app!)

Just some thoughts, If you have anything to add, please do so in the comments.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-22

OSX Receipt validation in Swift, part 8: Using Receigen

With just a little trickery, it is possible to use Receigen to do the hard work.
First of, you will need to purchase the Receigen tool. It is available from the App Store.

Once you have Receigen, start the application, click on "Code Assistant", in the new window click "Continue", fill in the fields as appropriate for your App.

Then click "Continue" and generate a file with "Save As ...". For this example, let's call it "receigen.h". Add this file to your project, but don't add it to the bridging header file.

Unfortunately it is not possible to directly call the CheckReceiptAndRun operation in receigen.h, that will just create linking errors.

But it is possible to call it through an intermediate.

So, create an Objective-C file as intermediary, I called mine "startup". Create both the header and implementation files. In the header file (Startup.h) write:

//
//  Startup.h
//

#ifndef MyGreatApp_Startup_h
#define MyGreatApp_Startup_h

int startup(int argc, const char * argv[]);

#endif

In the implementation file (Startup.m) write:

//
//  Startup.m
//

#import <Foundation/Foundation.h>
#import "receigen.h"

int startup(int argc, const char * argv[])
{
    return CheckReceiptAndRun(argc, (const char **)argv);
}

Add the "Startup.h" file to the bridging header file.

I think it is better to keep the intermediary (Startup) and the file generated by Receigen separate. When we create a new release of the App, the only thing we have to do is to regenerate and replace the receigen.h file. The Startup files are unaffected.

In main.swift replace the code with:

//
//  main.swift
//

startup(Process.argc, UnsafeMutablePointer<UnsafePointer<Int8>>(Process.unsafeArgv))

Now compile and test the code. It should run without an hitch.

Congrats, your app now has properly (IMO!) implemented Receipt Validation & Verification code.

Note: If you use Receigen, you do need to add openSSL to your App. But it is not necessary to import the apple root certificate. Accessing the C-union parts can be skipped as well, as can the determination of the GUID.

Note: If your project does not have a "main.swift" file, then it probably has an annotation in your AppDelegate: @NSApplicationMain If that is the case, simply remove the annotation from the AppDelegate and create the "main.swift" file manually. It must be called main.swift though!

See Receigen at work in my Port Spy app in the App Store. A utility that helps you debug your socket based application and includes its own source code. So you can see first hand how to implement socket based io in Swift as well as Receigen usage. And you will be helping this blog!

Happy coding

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-21

OSX Receipt validation in Swift, part 7: Testing the receipt validation

This part is about testing the Receipt Validation. At least partly: the success oriented approach. I.e. Only testing that the Receipt Validation works for a valid receipt. To perform a complete test, we would need a series of Receipts, each with a different error in it. But since the example code published in part6 is so simple, we just trust open SSL and the other tidbits to do their job.

How to get a receipt?

This is simple, but you must create a test user first. You will have to do that in iTunes Connect. In the module "Users and Roles" you will find a tab "Sandbox Testers". In there you can click the "plus" sign to add a tester. (This must be a different email address than your admin account!)

When done, enter Xcode and build your application. Make sure you use a developer certificate (perhaps confusingly this means that you should select signing identity "Mac App Store" for your target!)

The app does not have a receipt yet, so we have to fetch a receipt. To do this, we have to double click the App icon. Dive into the build directory from Xcode and find your App. Since the place of the build directory is configurable, I cannot tell you where it is. You can use the Xcode -> Preferences -> Locations -> Advanced panel to find out where it should be. When found, double click the app Icon. A window should appear asking you for your app store credentials. Use your test account (This is important!).

Next, the app should launch. If it does, congrats, the validation worked. If it did not, don't panic, simply get back into Xcode and set a break point at the start of the validation process. The previous steps have placed a certificate in your app bundle, and that certificate will remain there until you do something that removes it, like deleting the app. As long as you stick with Xcode the receipt will remain in place. Even when you create a new build.

Now, with the breakpoint in place step through the validation code until you spot the bug.

Fix it, rebuild, and test again. The receipt will be preserved during builds, so you don't have to fetch a new receipt every time.

Everything works?

OK, then now the real jobs starts: OBFUSCATE the validation code. Be as creative as you can and try to think like a hacker. How would you break your code? Then make sure you cannot. In part 9 of this series I have added some thoughts about obfuscation.

Obfuscation is a lot of work and it has to be done carefully, so that is why in part 8 I will show how I use Receigen to generate the obfuscation code for me :-)

2015-06-08: If you create a new version of the application, Xcode will still use the old receipt. When you then start the application you will get an error message: "... is damaged and can't be openend. Delete ... and download it again from the App Store.".
In this case, open up the App package by right-click on the Application icon, select "Show Package Contents" from the popup menu, navigate to the "Contents/_MASReceipt" and remove the "receipt" file. Then double click the icon again. Now you will be asked again for your test account credentials.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2015-05-20

OSX Receipt validation in Swift, part 6: Example code

Without further ado, here is complete code listing:

//
//  main.swift
//

import Cocoa
import IOKit

let errorCode: Int32 = 173

// Check if receipt is available

let mainBundle = NSBundle.mainBundle()
let receiptURL = mainBundle.appStoreReceiptURL
var error: NSError?
if (receiptURL?.checkResourceIsReachableAndReturnError(&error) == nil) {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt is not available")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt is available")


// Loading the receipt file

let receiptBIO = BIO_new(BIO_s_mem())
if let receiptData = NSData(contentsOfURL: receiptURL!) {
    BIO_write(receiptBIO, receiptData.bytes, Int32(receiptData.length))
} else {
    log.atLevelDebug(id: 0, source: "Main", message: "Could not read receipt data")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt data read")


// Parse the PKCS7 envelope

let receiptPKCS7 = d2i_PKCS7_bio(receiptBIO, nil)

if receiptPKCS7 == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt PKCS7 container parsing error")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt PKCS7 parsed ok")


// Check for a signature

if OBJ_obj2nid(receiptPKCS7.memory.type) != NID_pkcs7_signed {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt is not signed")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt PKCS7 is signed")


// Check for data

if OBJ_obj2nid(pkcs7_d_sign(receiptPKCS7).memory.contents.memory.type) != NID_pkcs7_data {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt does not contain signed data")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt contains signed data")


// Load the apple root certificate

let appleRootData: NSData!
if let appleRootURL = NSBundle.mainBundle().URLForResource("AppleIncRootCertificate", withExtension: "cer") {
    appleRootData = NSData(contentsOfURL: appleRootURL)
    if (appleRootData == nil) || (appleRootData.length == 0) {
        log.atLevelDebug(id: 0, source: "Main", message: "Could not load the Apple root certificate")
        exit(errorCode)
    }
} else {
    log.atLevelDebug(id: 0, source: "Main", message: "Apple root certificate URL not found")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Apple root certificate loaded")


// Verify the receipt signature

let appleRootBIO = BIO_new(BIO_s_mem())
BIO_write(appleRootBIO, appleRootData.bytes, Int32(appleRootData.length))
let appleRootX509 = d2i_X509_bio(appleRootBIO, nil)
let store = X509_STORE_new()
X509_STORE_add_cert(store, appleRootX509)
OpenSSL_add_all_digests()
let result = PKCS7_verify(receiptPKCS7, nil, store, nil, nil, 0)
if result != 1 {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt signature verification failed")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt signature verification ok")


// Extract the data set to be verified from the receipt

let octets = pkcs7_d_data(pkcs7_d_sign(receiptPKCS7).memory.contents)
var ptr = UnsafePointer<UInt8>(octets.memory.data)
let end = ptr.advancedBy(Int(octets.memory.length))

var type: Int32 = 0
var xclass: Int32 = 0
var length = 0

ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr)
if (type != V_ASN1_SET) {
    log.atLevelDebug(id: 0, source: "Main", message: "Could not read a ASN1 set from the receipt")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "An ASN1 set was read from the receipt")


// Extract the validation data from the set

var bundleIdString: NSString?
var bundleVersionString: NSString?
var bundleIdData: NSData?
var hashData: NSData?
var opaqueData: NSData?
var expirationDate: NSDate?

let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"
formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0)

while (ptr < end) {
    
    var integer: UnsafeMutablePointer<ASN1_INTEGER>
    
    // Expecting an attribute sequence
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr)
    if type != V_ASN1_SEQUENCE {
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: expected an attribute sequence")
        exit(errorCode)
    }
    
    let seq_end = ptr.advancedBy(length)
    var attr_type = 0
    var attr_version = 0
    
    // The attribute is an integer
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr)
    if type != V_ASN1_INTEGER {
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: attribute not an integer")
        exit(errorCode)
    }
    integer = c2i_ASN1_INTEGER(nil, &ptr, length)
    attr_type = ASN1_INTEGER_get(integer)
    ASN1_INTEGER_free(integer)
    
    // The version is an integer
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr)
    if type != V_ASN1_INTEGER {
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: version not an integer")
        exit(errorCode)
    }
    integer = c2i_ASN1_INTEGER(nil, &ptr, length);
    attr_version = ASN1_INTEGER_get(integer);
    ASN1_INTEGER_free(integer);

    // The attribute value is an octet string
    ASN1_get_object(&ptr, &length, &type, &xclass, end - ptr)
    if type != V_ASN1_OCTET_STRING {
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: value not an octet string")
        exit(errorCode)
    }

    switch (attr_type) {
    case 2:
        // Bundle identifier
        var str_ptr = ptr
        var str_type: Int32 = 0
        var str_length = 0
        var str_xclass: Int32 = 0
        ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr)
        if str_type == V_ASN1_UTF8STRING {
            // We store both the decoded string and the raw data for later
            // The raw is data will be used when computing the GUID hash
            bundleIdString = NSString(bytes: str_ptr, length: str_length, encoding: NSUTF8StringEncoding)
            bundleIdData = NSData(bytes: ptr, length: length)
            log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Read bundle identifier \(bundleIdString)")
        }
        
    case 3:
        // Bundle version
        var str_ptr = ptr
        var str_type: Int32 = 0
        var str_length = 0
        var str_xclass: Int32 = 0
        ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr)
        if str_type == V_ASN1_UTF8STRING {
            // We store the decoded string for later
            bundleVersionString = NSString(bytes: str_ptr, length: str_length, encoding: NSUTF8StringEncoding)
            log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Read bundle version \(bundleVersionString)")
        }
        break;
        
    case 4:
        // Opaque value
        opaqueData = NSData(bytes: ptr, length: length)
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Read opaque value of \(opaqueData!.length) bytes")
        
    case 5:
        // Computed GUID (SHA-1 Hash)
        hashData = NSData(bytes: ptr, length: length)
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Read hashData (GUID) of \(hashData!.length) bytes")

    case 21:
        // Expiration date
        var str_ptr = ptr
        var str_type: Int32 = 0
        var str_length = 0
        var str_xclass: Int32 = 0
        ASN1_get_object(&str_ptr, &str_length, &str_type, &str_xclass, seq_end - str_ptr)
        if str_type == V_ASN1_IA5STRING {
            // The date is stored as a string that needs to be parsed
            let dateString = String(NSString(bytes: str_ptr, length: str_length, encoding:NSASCIIStringEncoding)!)
            expirationDate = formatter.dateFromString(dateString)
            log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Read expirationDate \(dateString)")
        }
        
    default: //
        log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Ignored value of type \(attr_type)")
    }
    
    // Move past the value
    ptr = ptr.advancedBy(length)
}
log.atLevelDebug(id: 0, source: "Main", message: "ASN1: Parsing ended")


// Verify for completeness

if bundleIdString == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: Missing bundleIdString")
    exit(errorCode)
}
if bundleVersionString == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: Missing bundleVersionString")
    exit(errorCode)
}
if opaqueData == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: Missing opaqueData")
    exit(errorCode)
}
if hashData == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "ASN1 error: Missing hashData")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "All necessary data read from receipt")


// Verify bundleIdString

if bundleIdString != "com.mycompany.MyGreatApp" {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification error: Wrong bundle identifier")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification: Bundle identifier OK")


// Verify version

if bundleVersionString != "1.0" {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification error: Wrong bundle version")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification: Bundle version OK")


// Retrieve the Device GUID

let guidDataOrNil = copy_mac_address()
if guidDataOrNil == nil {
    log.atLevelDebug(id: 0, source: "Main", message: "Could not retrieve the device GUID")
    exit(errorCode)
}
let guidData: NSData = guidDataOrNil.takeRetainedValue()
log.atLevelDebug(id: 0, source: "Main", message: "Device GUID retrieved OK")


// Verify the hash

var hash = Array<UInt8>(count: 20, repeatedValue: 0)
var ctx = SHA_CTX()
SHA1_Init(&ctx)
SHA1_Update(&ctx, guidData.bytes, guidData.length)
SHA1_Update(&ctx, opaqueData!.bytes, opaqueData!.length)
SHA1_Update(&ctx, bundleIdData!.bytes, bundleIdData!.length)
SHA1_Final(&hash, &ctx)

let computedHashData = NSData(bytes: &hash, length: 20)
if !computedHashData.isEqualToData(hashData!) {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification error: Wrong hash")
    exit(errorCode)
}
log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification: Hash OK")


// Verify the expiration data for volume purchases only

if let expDate = expirationDate {
    if expDate.compare(NSDate()) == NSComparisonResult.OrderedAscending {
        log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification error: Volume purchase, expiration date expired")
        exit(errorCode)
    }
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification: Volume purchase, expiration date OK")
} else {
    log.atLevelDebug(id: 0, source: "Main", message: "Receipt verification: No volume purchase")
}


NSApplicationMain(Process.argc, Process.unsafeArgv)

Once run, it produced this output:

2015-05-14T11:56:16.915+0200, DEBUG    : 0, Main, Receipt is available
2015-05-14T11:56:16.916+0200, DEBUG    : 0, Main, Receipt data read
2015-05-14T11:56:16.917+0200, DEBUG    : 0, Main, Receipt PKCS7 parsed ok
2015-05-14T11:56:16.917+0200, DEBUG    : 0, Main, Receipt PKCS7 is signed
2015-05-14T11:56:16.917+0200, DEBUG    : 0, Main, Receipt contains signed data
2015-05-14T11:56:28.853+0200, DEBUG    : 0, Main, Apple root certificate loaded
2015-05-14T11:56:40.226+0200, DEBUG    : 0, Main, Receipt signature verification ok
2015-05-14T11:57:03.542+0200, DEBUG    : 0, Main, An ASN1 set was read from the receipt
2015-05-14T11:59:36.863+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 8
2015-05-14T12:00:25.226+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 1
2015-05-14T12:00:53.299+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 11
2015-05-14T12:00:59.281+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 14
2015-05-14T12:01:02.153+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 15
2015-05-14T12:01:04.024+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 16
2015-05-14T12:01:13.261+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 25
2015-05-14T12:01:22.835+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 10
2015-05-14T12:01:24.472+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 13
2015-05-14T12:02:07.101+0200, DEBUG    : 0, Main, ASN1: Read bundle version Optional(1.0)
2015-05-14T12:02:17.460+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 19
2015-05-14T12:02:20.516+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 9
2015-05-14T12:02:30.259+0200, DEBUG    : 0, Main, ASN1: Read opaque value of 16 bytes
2015-05-14T12:02:54.578+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 0
2015-05-14T12:03:18.148+0200, DEBUG    : 0, Main, ASN1: Read hashData (GUID) of 20 bytes
2015-05-14T12:03:21.558+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 12
2015-05-14T12:03:23.663+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 18
2015-05-14T12:03:51.503+0200, DEBUG    : 0, Main, ASN1: Read bundle identifier Optional(com.mycompany.MyGreatApp)
2015-05-14T12:04:01.350+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 6
2015-05-14T12:04:03.221+0200, DEBUG    : 0, Main, ASN1: Ignored value of type 7
2015-05-14T12:04:06.355+0200, DEBUG    : 0, Main, ASN1: Parsing ended
2015-05-14T12:04:21.875+0200, DEBUG    : 0, Main, All necessary data read from receipt
2015-05-14T12:04:41.318+0200, DEBUG    : 0, Main, Receipt verification: Bundle identifier OK
2015-05-14T12:05:01.917+0200, DEBUG    : 0, Main, Receipt verification: Bundle version OK
2015-05-14T12:05:24.411+0200, DEBUG    : 0, Main, Device GUID retrieved OK
2015-05-14T12:06:03.672+0200, DEBUG    : 0, Main, Receipt verification: Hash OK
2015-05-14T12:06:10.627+0200, DEBUG    : 0, Main, Receipt verification: No volume purchase

The only thing I did change before copy & pasting the code was the bundle identifier, otherwise this is the code that is in "main.swift".

Note that main.swift is not always generated. The later versions of xcode omit this file in favour of a directive in AppDelegate. It is possible to remove the directive and supply a main.swift yourself.

The code above is pretty much a direct translation of the code in the article from Laurent Etiemble. With a few modifications that are inevitable since openSSL and Xcode are now later versions, and of course this is Swift.

Oh, did I mention (I did!) that you should not use the above code? While it demonstrates what needs to be done, it does not show how to obfuscate the whole process. That is something you should figure out yourselves! (Any hacker would hack the above code in less time than it takes you to fetch a cup of coffee!)

Btw: I used my own logging framework (SwifterLog). You can replace that with your own, or fetch mine from github. See the link at the right hand side.

Next up: part 7, testing the receipt validation

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.