MYKeychainItem.m
author snej@snej.local
Sat Apr 04 20:42:03 2009 -0700 (2009-04-04)
changeset 0 0a6527af039b
child 1 60e4cbbb5128
permissions -rw-r--r--
Initial checkin. Passes tests on Mac and in iPhone simulator.
     1 //
     2 //  MYKeychainItem.m
     3 //  MYCrypto
     4 //
     5 //  Created by Jens Alfke on 3/26/09.
     6 //  Copyright 2009 Jens Alfke. All rights reserved.
     7 //
     8 
     9 #import "MYKeychainItem.h"
    10 #import "MYCrypto_Private.h"
    11 #import "MYErrorUtils.h"
    12 
    13 
    14 NSString* const MYCSSMErrorDomain = @"CSSMErrorDomain";
    15 
    16 
    17 @implementation MYKeychainItem
    18 
    19 
    20 - (id) initWithKeychainItemRef: (MYKeychainItemRef)itemRef;
    21 {
    22     Assert(itemRef!=NULL);
    23     self = [super init];
    24     if (self != nil) {
    25         _itemRef = itemRef;
    26         CFRetain(_itemRef);
    27     }
    28     return self;
    29 }
    30 
    31 
    32 @synthesize keychainItemRef=_itemRef;
    33 
    34 - (void) dealloc
    35 {
    36     if (_itemRef) CFRelease(_itemRef);
    37     [super dealloc];
    38 }
    39 
    40 - (id) copyWithZone: (NSZone*)zone {
    41     // As keys are immutable, it's not necessary to make copies of them. This makes it more efficient
    42     // to use instances as NSDictionary keys or store them in NSSets.
    43     return [self retain];
    44 }
    45 
    46 - (BOOL) isEqual: (id)obj {
    47     // Require the objects to be of the same class, so that a MYPublicKey will not be equal to a
    48     // MYKeyPair with the same public key.
    49     return (obj == self) || 
    50            ([obj class] == [self class] && CFEqual(_itemRef, [obj keychainItemRef]));
    51 }
    52 
    53 - (NSUInteger) hash {
    54     return CFHash(_itemRef);
    55 }
    56 
    57 - (NSArray*) _itemList {
    58     return $array((id)_itemRef);
    59 }
    60 
    61 #if USE_IPHONE_API
    62 - (CFDictionaryRef) asQuery {
    63     return (CFDictionaryRef) $dict( {(id)kSecClass, (id)kSecClassKey},//FIX
    64                                     {(id)kSecMatchItemList, self._itemList} );
    65 }
    66 #endif
    67 
    68 
    69 - (MYKeychain*) keychain {
    70 #if USE_IPHONE_API
    71     return [MYKeychain defaultKeychain];
    72 #else
    73     MYKeychain *keychain = nil;
    74     SecKeychainRef keychainRef = NULL;
    75     if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) {
    76         if (keychainRef) {
    77             keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease];
    78             CFRelease(keychainRef);
    79         }
    80     }
    81     return keychain;
    82 #endif
    83 }
    84 
    85 - (BOOL) removeFromKeychain {
    86 #if USE_IPHONE_API
    87     return check(SecItemDelete(self.asQuery), @"SecItemDelete");
    88 #else
    89     return check(SecKeychainItemDelete((SecKeychainItemRef)_itemRef), @"SecKeychainItemDelete");
    90 #endif
    91 }
    92 
    93 
    94 #pragma mark -
    95 #pragma mark DATA / METADATA ACCESSORS:
    96 
    97 
    98 - (NSData*) _getContents: (OSStatus*)outError {
    99     NSData *contents = nil;
   100 #if USE_IPHONE_API
   101 #else
   102 	UInt32 length = 0;
   103     void *bytes = NULL;
   104     *outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes);
   105     if (!*outError && bytes) {
   106         contents = [NSData dataWithBytes: bytes length: length];
   107         SecKeychainItemFreeAttributesAndData(NULL, bytes);
   108     }
   109 #endif
   110     return contents;
   111 }
   112 
   113 + (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   114     NSData *value = nil;
   115 #if USE_IPHONE_API
   116     NSDictionary *info = $dict( {(id)kSecClass, (id)kSecClassKey},
   117                                 {(id)kSecMatchItemList, $array((id)item)},
   118                                 {(id)kSecReturnAttributes, $true} );
   119     CFDictionaryRef attrs;
   120     if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching"))
   121         return nil;
   122     CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr);
   123     value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil;
   124     CFRelease(attrs);
   125     
   126 #else
   127 	UInt32 format = kSecFormatUnknown;
   128 	SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format};
   129     SecKeychainAttributeList *list = NULL;
   130 	
   131     if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info,
   132                                                    NULL, &list, NULL, NULL),
   133               @"SecKeychainItemCopyAttributesAndData")) {
   134         if (list) {
   135             if (list->count == 1)
   136                 value = [NSData dataWithBytes: list->attr->data
   137                                        length: list->attr->length];
   138             else if (list->count > 1)
   139                 Warn(@"Multiple values for keychain item attribute");
   140             SecKeychainItemFreeAttributesAndData(list, NULL);
   141         }
   142     }
   143 #endif
   144     return value;
   145 }
   146 
   147 + (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   148     NSData *value = [self _getAttribute: attr ofItem: item];
   149     if (!value) return nil;
   150     const char *bytes = value.bytes;
   151     size_t length = value.length;
   152     if (length>0 && bytes[length-1] == 0)
   153         length--;           // Some values are null-terminated!?
   154     NSString *str = [[NSString alloc] initWithBytes: bytes length: length
   155                                            encoding: NSUTF8StringEncoding];
   156     if (!str)
   157         Warn(@"MYKeychainItem: Couldn't decode attr value as string");
   158     return [str autorelease];
   159 }
   160 
   161 - (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr {
   162     return [[self class] _getStringAttribute: attr ofItem: _itemRef];
   163 }
   164 
   165 
   166 + (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item
   167            stringValue: (NSString*)stringValue
   168 {
   169 #if USE_IPHONE_API
   170     id value = stringValue ?(id)stringValue :(id)[NSNull null];
   171     NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey},
   172                                 {(id)kSecAttrKeyType, (id)attr},
   173                                 {(id)kSecMatchItemList, $array((id)item)});
   174     NSDictionary *attrs = $dict({(id)attr, value});
   175     return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate");
   176     
   177 #else
   178     NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding];
   179     SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes};
   180 	SecKeychainAttributeList list = {.count=1, .attr=&attribute};
   181     return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL),
   182                  @"SecKeychainItemModifyAttributesAndData");
   183 #endif
   184 }
   185 
   186 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   187     return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr];
   188 }
   189 
   190 
   191 @end
   192 
   193 
   194 
   195 
   196 BOOL check(OSStatus err, NSString *what) {
   197     if (err) {
   198 #if !USE_IPHONE_API
   199         if (err < -2000000000)
   200             return checkcssm(err,what);
   201 #endif
   202         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err));
   203         return NO;
   204     } else
   205         return YES;
   206 }
   207 
   208 #if !USE_IPHONE_API
   209 BOOL checkcssm(CSSM_RETURN err, NSString *what) {
   210     if (err != CSSM_OK) {
   211         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err));
   212         return NO;
   213     } else
   214         return YES;
   215 }
   216 #endif