Basic Security in iOS 5 – Part 2

This is a post by Chris Lowe, an iOS application developer and aspiring game developer living in San Antonio, Texas. Welcome to Part Two of the epic tutorial series on implementing basic security in iOS 5! In Part One, we began work on Christmas Keeper, an app designed to keep track of holiday gift ideas. […] By Chris Lowe.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Keeping Track of Christmas

Now back to our Christmas Keeper app. When we left off last time, we had finished implementing user authentication, necessary to protect the user’s Christmas lists from prying eyes!

Successful authentication should have brought us to our UITableView of Christmas presents, but currently our app is missing logic to handle anything with the table, so let’s remedy the situation.

First off, we are going to create a model to store our Christmas presents and manage several other (mostly housekeeping) things for us. Create a new file with the iOS\Cocoa Touch\Objective-C class template, enter ChristmasItems for the class, and make it a subclass of NSObject. Then replace ChristmasItems.h with the following:

#import 

@interface ChristmasItems : NSObject
@property (nonatomic, strong) NSMutableArray *christmasList;

// Default initializer.
- (id)initWithJSONFromFile:(NSString *)jsonFileName;

// Exposed method to load the data directly into the 'christmasList' mutable array.
- (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName;

// Helper method to ensure that we have a default image for users who don't specify a new picture.
- (void)writeDefaultImageToDocuments;

// Writes JSON file to disk, using Data Protection API.
- (void)saveChristmasGifts;

// Should someone delete a present, we can remove it directly, then save the new list to disk (saveChristmasGifts).
- (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath;

// Should someone add a present, we can add it directly, then save the new list to disk (saveChristmasGifts).
- (void)addPresentToChristmasList:(NSDictionary *)newItem;

// Accessor method to retrieve the saved image for a given present.
- (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath;

// Accessor method to retrieve the saved text for a given present.
- (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath;

// Accessor method to retrieve the saved image name for a given present.
- (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath;

@end

The first thing we do is create an array to actually hold our Christmas elements (which will just be a dictionary). For the rest of the header, we have several methods for setting up our Christmas list, as well as for accessing details about individual presents. Read through the comments to get an overview of what they will do.

Now switch to ChristmasItems.m and replace the contents with the following:

#import "ChristmasItems.h"

@implementation ChristmasItems
@synthesize christmasList;

- (id)initWithJSONFromFile:(NSString *)jsonFileName 
{
    self = [super init];
    if (self) {
        self.christmasList = [self dataFromJSONFile:jsonFileName];
        [self writeDefaultImageToDocuments];
    }
    return self;
}

// Helper method to get the data from the JSON file.
- (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName 
{
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *jsonPath = [documentsDirectory stringByAppendingPathComponent:jsonFileName];
    if ([[NSFileManager defaultManager] fileExistsAtPath:jsonPath]) {
        NSError* error = nil;
        NSData *responseData = [NSData dataWithContentsOfFile:jsonPath];
        NSDictionary* json = [NSJSONSerialization JSONObjectWithData:responseData options:kNilOptions error:&error];        
        return [[NSMutableArray alloc] initWithArray:[json objectForKey:@"gifts"]];
    }
    
    // We have to have a default cell. :]
    return [[NSMutableArray alloc] initWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:@"Thank Chris Lowe for an awesome tutorial on Basic iOS Security! Maybe follow him on Twitter (@chrisintx)?", @"text", @"noImage", @"imageName", nil], nil];
}

// Helper method to save the JSON file.
// Here we are write-protecting the file, then we are setting the file itself to use File Protection (Data At Rest).
- (void)saveChristmasGifts 
{
    
    NSError *error = nil;
    // We wrap our Gifts array inside of a dictionary to follow the standard JSON format (you could keep it as an array).
    NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObject:self.christmasList forKey:@"gifts"];
    NSData* jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary 
                                                       options:NSJSONWritingPrettyPrinted error:&error];
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *jsonPath = [documentsDirectory stringByAppendingPathComponent:@"christmasItems.json"];
    [jsonData writeToFile:jsonPath options:NSDataWritingFileProtectionComplete error:&error];
    [[NSFileManager defaultManager] setAttributes:[NSDictionary dictionaryWithObject:NSFileProtectionComplete forKey:NSFileProtectionKey] ofItemAtPath:jsonPath error:&error];
}

// This method ensures that we always have an image available in case the user doesn't specify one.
- (void)writeDefaultImageToDocuments 
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:@"default_image.png"];
    // If the file does NOT exist, then save it
    if (![[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        UIImage *editedImage = [UIImage imageNamed:@"present.png"];
        NSData *webData = UIImagePNGRepresentation(editedImage);
        [webData writeToFile:imagePath atomically:YES];
    }
}

// Accessor method to retrieve the saved text for a given present.
- (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath 
{
    return  [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"text"];
}

// Accessor method to retrieve the saved image name for a given present.
- (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath 
{
    return [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"];
}

- (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath 
{
    // Admittedly not the most efficient way to set an image. :]
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imageKey = [[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageKey];
    UIImage *presentImage = nil;
    if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath]) {
        presentImage = [UIImage imageWithContentsOfFile:imagePath];
    } else { // If we dont have an "imageName" key then we use the default one
        presentImage = [UIImage imageNamed:@"present.png"];
    }
    return presentImage;
}

- (void)deleteImageWithName:(NSString *)imageName 
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:imageName];
    // Delete the file, but not the default image. (We need that one!)
    if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath] && (![@"default_image.png" isEqualToString:imageName])) {
        [[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil];
    }
}

- (void)addPresentToChristmasList:(NSDictionary *)newItem 
{
    [self.christmasList addObject:newItem];
    [self saveChristmasGifts];
}

- (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath 
{
    [self deleteImageWithName:[[self.christmasList objectAtIndex:indexPath.row] objectForKey:@"imageName"]];
    [self.christmasList removeObjectAtIndex:indexPath.row];
    [self saveChristmasGifts];
}

@end

Let’s walk through each method here:

  • – (id)initWithJSONFromFile:(NSString *)jsonFileName
    This method is our “default initializer.” This is the method we want people to use when creating our class. In this method, we initialize our array to whatever we find in our “dataFromJSONFile” method. We also save some placeholder images for later use.
  • – (NSMutableArray *)dataFromJSONFile:(NSString *)jsonFileName
    In this method, we look for a specified JSON file in our documents directory. If we find it, we return the list at key “gift.” If we don’t find it, we create a basic single-element array and return that instead.
  • – (void)saveChristmasGifts
    When someone adds/removes/changes a gift, we call this method to write the JSON list to disk so that we can always have the latest Christmas Gift list saved.
  • – (void)writeDefaultImageToDocuments
    Saves our default present image so that all of our images are stored in one place.
  • – (NSString *)textForPresentAtIndex:(NSIndexPath *)indexPath
    Returns the text (the present details) for a given present (at index).
  • – (UIImage *)imageForPresentAtIndex:(NSIndexPath *)indexPath
    Same as above, but for the image of the actual present.
  • – (NSString *)imageNameForPresentAtIndex:(NSIndexPath *)indexPath
    Same as above, but for the image name of the present. This is important so that we can pass the name of the picture around instead of the actual picture.
  • – (void)deleteImageWithName:(NSString *)imageName
    We provide the ability for people to delete presents from the list. This method deletes the image associated with the deleted present.
  • – (void)addPresentToChristmasList:(NSDictionary *)newItem
    We add the present to our array and update the JSON file.
  • – (void)removeGiftAtIndexPath:(NSIndexPath *)indexPath
    We can also remove presents. This method deletes the present image, removes it from the array, and updates the JSON file.

The most important security-related code here is the saveChristmasGifts method. This method writes a JSON file to disk using “NSDataWritingFileProtectionComplete,” which protects our file from being accessed WHILE the file is being written.

We also need the next line, which uses “NSFileProtectionComplete” to protect the file “at rest” – i.e., while not being accessed. You’ll get the best results when the user has a passcode lock on their device – your app can only be ready for the user to protect themselves.

Protecting the file while it is being written is important because you don’t want to give someone the chance to modify your file while its open (being written to) such as changing/deleting content or, in a more extreme case, copy the contents somewhere else. There is a small chance of this on an iOS device and is mostly related to the Mac but it’s still good practice all around (plus, you never know when you’ll be porting your iOS app to the Mac :)). The same goes for NSFileProtectionComplete in that you don’t want to allow the file to be accessed while the device is locked because that could mean something bad is happening in general (such as the phone being stolen and the thief is trying to pry open the users data).

In the spirit of full disclosure, saving to the Documents directory is admittedly less secure than say to Caches (NSCachesDirectory) or Temp (NSTemporaryDirectory) directories but its more well known and easier to use as an example in the blog post since people know where to look for it. Conversely, never save sensitive information to NSUserDefaults – these values are stored in PLAIN TEXT (i.e. just like this blog post) for all to see (and is also saved inside of your application bundle). Be mindful of what you put in there.

Chris Lowe

Contributors

Chris Lowe

Author

Over 300 content creators. Join our team.