Search code examples
iphoneioskeychainautomatic-ref-countingsfhfkeychainutils

SFHFKeychainUtils. iOS keychain. ARC compatible


I was wondering if anyone that was using the SFHFKeychainUtils managed to modify them to be compatible for ARC. More exactly the

NSDictionary *attributeResult = NULL;
NSMutableDictionary *attributeQuery = [query mutableCopy];
[attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge id) kSecReturnAttributes];
OSStatus status = SecItemCopyMatching((CFDictionaryRef) attributeQuery,(CFTypeRef *)(attributeResult));

I tried

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery,(CFTypeRef *)(attributeResult));

also

CFTypeRef subAttributeResult = (CFTypeRef *)(objc_unretainedPointer(attributeResult));
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef) attributeQuery,(CFTypeRef *)(subAttributeResult));

These 2 being the only 2 approaches I managed to get no errors for. Any other approach by adding objc_XXX instead of CFTypeRef here and there gets me errors(from implicit conversions from obj-c pointer to CFTypeRef is disallowed in ARC, to passing x parameter to y parameter discards qualifiers). Obviously the first snip of code gives an error too. Although I get no errors when building, app crashes with EXC_BAD_ACCESS when reaching this part of code.

The link for the full SFHFKeychainUtils: https://github.com/ldandersen/scifihifi-iphone/tree/master/security

Any help, please? Thank you.


Solution

  • here is the ARC compatible SFHFKeychainUtils,

    SFHFKeychainUtils.h

    //
    //  SFHFKeychainUtils.h
    //
    //  Created by Buzz Andersen on 10/20/08.
    //  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.
    //  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.
    //
    //  Permission is hereby granted, free of charge, to any person
    //  obtaining a copy of this software and associated documentation
    //  files (the "Software"), to deal in the Software without
    //  restriction, including without limitation the rights to use,
    //  copy, modify, merge, publish, distribute, sublicense, and/or sell
    //  copies of the Software, and to permit persons to whom the
    //  Software is furnished to do so, subject to the following
    //  conditions:
    //
    //  The above copyright notice and this permission notice shall be
    //  included in all copies or substantial portions of the Software.
    //
    //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    //  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    //  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    //  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    //  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    //  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    //  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    //  OTHER DEALINGS IN THE SOFTWARE.
    //
    
    #import <UIKit/UIKit.h>
    
    
    @interface SFHFKeychainUtils : NSObject {
    
    }
    
    + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
    + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
    + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
    
    @end
    

    SFHFKeychainUtils.m

    //  
    //  SFHFKeychainUtils.m 
    //
    
    //  Created by Buzz Andersen on 10/20/08.   
    //  Based partly on code by Jonathan Wight, Jon Crosby, and Mike Malone.    
    //  Copyright 2008 Sci-Fi Hi-Fi. All rights reserved.   
    //
    
    //  Permission is hereby granted, free of charge, to any person 
    //  obtaining a copy of this software and associated documentation  
    //  files (the "Software"), to deal in the Software without 
    //  restriction, including without limitation the rights to use,    
    //  copy, modify, merge, publish, distribute, sublicense, and/or sell   
    //  copies of the Software, and to permit persons to whom the   
    //  Software is furnished to do so, subject to the following    
    //  conditions: 
    //
    
    //  The above copyright notice and this permission notice shall be  
    //  included in all copies or substantial portions of the Software. 
    //  
    //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
    //  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
    //  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND    
    //  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
    //  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,    
    //  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    
    //  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR   
    //  OTHER DEALINGS IN THE SOFTWARE. 
    //
    
    #import "SFHFKeychainUtils.h"   
    #import <Security/Security.h>   
    
    
    static NSString *SFHFKeychainUtilsErrorDomain = @"SFHFKeychainUtilsErrorDomain";
    
    #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
    
    @interface SFHFKeychainUtils (PrivateMethods)   
    (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error; 
    @end
    
    #endif
    
    @implementation SFHFKeychainUtils
    
    #if __IPHONE_OS_VERSION_MIN_REQUIRED < 30000 && TARGET_IPHONE_SIMULATOR
    
     +(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {       
          if (!username || !serviceName) {          
               *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];          
                return nil;         
              }      
    
          SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
          if (*error || !item) {            
                return nil;         
              }
    
          // from Advanced Mac OS X Programming, ch. 16     
          UInt32 length;        
          char *password;       
          SecKeychainAttribute attributes[8];       
          SecKeychainAttributeList list;
          attributes[0].tag = kSecAccountItemAttr;      
          attributes[1].tag = kSecDescriptionItemAttr;      
          attributes[2].tag = kSecLabelItemAttr;
          attributes[3].tag = kSecModDateItemAttr;
          list.count = 4;       
          list.attr = attributes;
         OSStatus status = SecKeychainItemCopyContent(item, NULL, &list, &length, (void **)&password);
          if (status != noErr) {            
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
                return nil;         
              }
         NSString *passwordString = nil;
         if (password != NULL) {        
               char passwordBuffer[1024];
                if (length > 1023) {                
                     length = 1023;             
                    }           
                strncpy(passwordBuffer, password, length);
                passwordBuffer[length] = '\0';          
                passwordString = [NSString stringWithCString:passwordBuffer];           
              }
          SecKeychainItemFreeContent(&list, password);
          CFRelease(item);
          return passwordString;        
        }
    
    + (void) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error {  
    
          if (!username || !password || !serviceName) {         
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];         
                return;         
              }
          OSStatus status = noErr;
          SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
          if (*error && [*error code] != noErr) {           
                return;         
              }
          *error = nil;     
    
          if (item) {           
                status = SecKeychainItemModifyAttributesAndData(item,NULL,strlen([password UTF8String]),[password UTF8String]);
                CFRelease(item);            
              }     
          else {            
                status = SecKeychainAddGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String],strlen([password UTF8String]),[password UTF8String],NULL);
    
              }
          if (status != noErr) {            
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
              }     
        }
    
    + (void) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {    
          if (!username || !serviceName) {          
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: 2000 userInfo: nil];          
                return;        
              }
    
          *error = nil;
    
          SecKeychainItemRef item = [SFHFKeychainUtils getKeychainItemReferenceForUsername: username andServiceName: serviceName error: error];
          if (*error && [*error code] != noErr) {        
              return;           
              }
    
          OSStatus status;    
          if (item) {        
               status = SecKeychainItemDelete(item);            
                CFRelease(item);        
              }
    
          if (status != noErr) {            
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
              }     
        }
    
    + (SecKeychainItemRef) getKeychainItemReferenceForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {    
          if (!username || !serviceName) {          
                *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];        
                return nil;         
              }
    
          *error = nil;    
          SecKeychainItemRef item;   
          OSStatus status = SecKeychainFindGenericPassword(NULL,strlen([serviceName UTF8String]),[serviceName UTF8String],strlen([username UTF8String]),[username UTF8String], NULL,NULL,&item);
    
          if (status != noErr) {        
                if (status != errSecItemNotFound) {             
                      *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];              
                    }        
                return nil;         
              }
              return item;    
        }
    
    #else
    
    + (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error {
    
          if (!username || !serviceName) {          
                if (error != nil) {             
                      *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];           
                    }           
                return nil;        
              }      
    
          if (error != nil) {           
                *error = nil;           
              }   
          // Set up a query dictionary with the base query attributes: item type (generic), username, and service    
         NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, nil];  
         NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, nil];    
         NSMutableDictionary *query = [[NSMutableDictionary alloc] initWithObjects: objects forKeys: keys];    
          // First do a query for attributes, in case we already have a Keychain item with no password data set.    
          // One likely way such an incorrect item could have come about is due to the previous (incorrect)    
          // version of this code (which set the password as a generic attribute instead of password data).   
          NSMutableDictionary *attributeQuery = [query mutableCopy];   
          [attributeQuery setObject: (id) kCFBooleanTrue forKey:(__bridge_transfer id) kSecReturnAttributes];       
           CFTypeRef attrResult = NULL;  
          OSStatus status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) attributeQuery, &attrResult);       
          //NSDictionary *attributeResult = (__bridge_transfer NSDictionary *)attrResult;   
          if (status != noErr) {        
                // No existing item found--simply return nil for the password           
               if (error != nil && status != errSecItemNotFound) {              
                      //Only return an error if a real exception happened--not simply for "not found."           
                      *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];              
                    }    
                return nil;         
              }
    
         // We have an existing item, now query for the password data associated with it.    
          NSMutableDictionary *passwordQuery = [query mutableCopy];    
          [passwordQuery setObject: (id) kCFBooleanTrue forKey: (__bridge_transfer id) kSecReturnData];     
            CFTypeRef resData = NULL;       
          status = SecItemCopyMatching((__bridge_retained CFDictionaryRef) passwordQuery, (CFTypeRef *) &resData);      
          NSData *resultData = (__bridge_transfer NSData *)resData;    
          if (status != noErr) {       
                if (status == errSecItemNotFound) {            
                      // We found attributes for the item previously, but no password now, so return a special error.               
                      // Users of this API will probably want to detect this error and prompt the user to               
                      // re-enter their credentials.  When you attempt to store the re-entered credentials              
                      // using storeUsername:andPassword:forServiceName:updateExisting:error           
                      // the old, incorrect entry will be deleted and a new one with a properly encrypted               
                      // password will be added.
    
                      if (error != nil) {                   
                            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];                 
                          }             
                    }       
                else {              
                     // Something else went wrong. Simply return the normal Keychain API error code.            
                      if (error != nil) {                   
                            *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];                    
                         }              
                    }           
               return nil;          
              }   
          NSString *password = nil;     
         if (resultData) {          
                password = [[NSString alloc] initWithData: resultData encoding: NSUTF8StringEncoding];          
              } 
          else {        
                // There is an existing item, but we weren't able to get password data for it for some reason,          
                // Possibly as a result of an item being incorrectly entered by the previous code.          
                // Set the -1999 error so the code above us can prompt the user again.
    
               if (error != nil) {      
                      *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -1999 userInfo: nil];               
                    }           
              }     
          return password;      
        }
    
    + (BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error 
    
    {           
         if (!username || !password || !serviceName)
    
              {            
                    if (error != nil)                   
                        {                       
                             *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];                    
                     }              
                    return NO;          
                  }
    
        // See if we already have a password entered for these credentials.
    
          NSError *getError = nil;      
          NSString *existingPassword = [SFHFKeychainUtils getPasswordForUsername: username andServiceName: serviceName error:&getError];        
    
         if ([getError code] == -1999)     
              {            
                    // There is an existing entry without a password properly stored (possibly as a result of the previous incorrect version of this code.
    
                    // Delete the existing item before moving on entering a correct one.            
                   getError = nil;          
    
                    [self deleteItemForUsername: username andServiceName: serviceName error: &getError];            
    
                    if ([getError code] != noErr)                 
                       {                   
                              if (error != nil)                         
                                  {                            
                                        *error = getError;                           
                                     }                      
                              return NO;                        
                           }            
                  }     
        else if ([getError code] != noErr)      
              {           
                    if (error != nil)                   
                        {                       
                              *error = getError;                    
                            }               
                    return NO;          
                  }     
         if (error != nil)          
              {          
                    *error = nil;               
                  }
    
          OSStatus status = noErr;
    
          if (existingPassword)         
              {
    
                    // We have an existing, properly entered item with a password.        
                    // Update the existing item.            
    
                   if (![existingPassword isEqualToString:password] && updateExisting)                
                        {                       
                              //Only update if we're allowed to update existing.  If not, simply do nothing.
    
                              NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,nil];
    
                              NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,nil];
    
                              NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];      
    
                              status = SecItemUpdate((__bridge_retained CFDictionaryRef) query, (__bridge_retained CFDictionaryRef) [NSDictionary dictionaryWithObject: [password dataUsingEncoding: NSUTF8StringEncoding] forKey: (__bridge_transfer NSString *) kSecValueData]);                      
                            }               
                  }     
          else      
              {             
                   // No existing entry (or an existing, improperly entered, and therefore now
    
                    // deleted, entry).  Create a new entry.
    
    
                    NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass,kSecAttrService,kSecAttrLabel,kSecAttrAccount,kSecValueData,nil];
    
                    NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword,serviceName,serviceName,username,[password dataUsingEncoding: NSUTF8StringEncoding],nil];
    
                    NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];            
    
                    status = SecItemAdd((__bridge_retained CFDictionaryRef) query, NULL);               
                  }     
          if (error != nil && status != noErr)          
              {            
                    // Something went wrong with adding the new item. Return the Keychain error code.               
                    *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];                
                    return NO;            
                  }     
          return YES;       
        }
    
    + (BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error    
    {       
          if (!username || !serviceName)        
              {            
                    if (error != nil)                   
                       {                        
                              *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: -2000 userInfo: nil];                       
                            }            
                    return NO;          
                  }     
          if (error != nil)       
              {             
                   *error = nil;            
                  }     
          NSArray *keys = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClass, kSecAttrAccount, kSecAttrService, kSecReturnAttributes, nil];     
          NSArray *objects = [[NSArray alloc] initWithObjects: (__bridge_transfer NSString *) kSecClassGenericPassword, username, serviceName, kCFBooleanTrue, nil];        
          NSDictionary *query = [[NSDictionary alloc] initWithObjects: objects forKeys: keys];      
          OSStatus status = SecItemDelete((__bridge_retained CFDictionaryRef) query);       
    
          if (error != nil && status != noErr)        
              {           
                    *error = [NSError errorWithDomain: SFHFKeychainUtilsErrorDomain code: status userInfo: nil];            
                    return NO;           
                  }    
          return YES;    
        }
    #endif
    @end