MYKeychainItem.m
author Jens Alfke <jens@mooseyard.com>
Sun Jun 07 21:53:56 2009 -0700 (2009-06-07)
changeset 23 39fec79de6e8
parent 14 3af1d1c0ceb5
child 24 6856e071d25a
permissions -rw-r--r--
A snapshot taken during the long, agonizing crawl toward getting everything running on iPhone.
     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         LogTo(INIT,@"%@, _itemRef=%@", [self class], itemRef);
    28     }
    29     return self;
    30 }
    31 
    32 
    33 @synthesize keychainItemRef=_itemRef;
    34 
    35 - (void) dealloc
    36 {
    37     if (_itemRef) CFRelease(_itemRef);
    38     [super dealloc];
    39 }
    40 
    41 - (void) finalize
    42 {
    43     if (_itemRef) CFRelease(_itemRef);
    44     [super finalize];
    45 }
    46 
    47 - (id) copyWithZone: (NSZone*)zone {
    48     // As keys are immutable, it's not necessary to make copies of them. This makes it more efficient
    49     // to use instances as NSDictionary keys or store them in NSSets.
    50     return [self retain];
    51 }
    52 
    53 - (BOOL) isEqual: (id)obj {
    54     return (obj == self) ||
    55            ([obj isKindOfClass: [MYKeychainItem class]] && CFEqual(_itemRef, [obj keychainItemRef]));
    56 }
    57 
    58 - (NSUInteger) hash {
    59     return CFHash(_itemRef);
    60 }
    61 
    62 - (NSString*) description {
    63     return $sprintf(@"%@[%p]", [self class], _itemRef);     //FIX: Can we do anything better?
    64 }
    65 
    66 
    67 - (NSArray*) _itemList {
    68     return $array((id)_itemRef);
    69 }
    70 
    71 #if MYCRYPTO_USE_IPHONE_API
    72 - (CFDictionaryRef) asQuery {
    73     return (CFDictionaryRef) $dict( {(id)kSecClass, (id)kSecClassKey},//FIX
    74                                     {(id)kSecMatchItemList, self._itemList} );
    75 }
    76 #endif
    77 
    78 
    79 - (MYKeychain*) keychain {
    80 #if MYCRYPTO_USE_IPHONE_API
    81     return [MYKeychain defaultKeychain];
    82 #else
    83     MYKeychain *keychain = nil;
    84     SecKeychainRef keychainRef = NULL;
    85     if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) {
    86         if (keychainRef) {
    87             keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease];
    88             CFRelease(keychainRef);
    89         }
    90     }
    91     return keychain;
    92 #endif
    93 }
    94 
    95 - (BOOL) removeFromKeychain {
    96     OSStatus err;
    97 #if MYCRYPTO_USE_IPHONE_API
    98     err = SecItemDelete(self.asQuery);
    99 #else
   100     err = SecKeychainItemDelete((SecKeychainItemRef)_itemRef);
   101     if (err==errSecInvalidItemRef)
   102         return YES;     // result for an item that's not in a keychain
   103 #endif
   104     return err==errSecItemNotFound || check(err, @"SecKeychainItemDelete");
   105 }
   106 
   107 
   108 #pragma mark -
   109 #pragma mark DATA / METADATA ACCESSORS:
   110 
   111 
   112 - (NSData*) _getContents: (OSStatus*)outError {
   113     NSData *contents = nil;
   114 #if MYCRYPTO_USE_IPHONE_API
   115 #else
   116 	UInt32 length = 0;
   117     void *bytes = NULL;
   118     *outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes);
   119     if (!*outError && bytes) {
   120         contents = [NSData dataWithBytes: bytes length: length];
   121         SecKeychainItemFreeAttributesAndData(NULL, bytes);
   122     }
   123 #endif
   124     return contents;
   125 }
   126 
   127 + (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   128     NSData *value = nil;
   129 #if MYCRYPTO_USE_IPHONE_API
   130     NSDictionary *info = $dict( {(id)kSecClass, (id)kSecClassKey},
   131                                 {(id)kSecMatchItemList, $array((id)item)},
   132                                 {(id)kSecReturnAttributes, $true} );
   133     CFDictionaryRef attrs;
   134     if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching"))
   135         return nil;
   136     CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr);
   137     value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil;
   138     CFRelease(attrs);
   139     
   140 #else
   141 	UInt32 format = kSecFormatUnknown;
   142 	SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format};
   143     SecKeychainAttributeList *list = NULL;
   144 	
   145     if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info,
   146                                                    NULL, &list, NULL, NULL),
   147               @"SecKeychainItemCopyAttributesAndData")) {
   148         if (list) {
   149             if (list->count == 1)
   150                 value = [NSData dataWithBytes: list->attr->data
   151                                        length: list->attr->length];
   152             else if (list->count > 1)
   153                 Warn(@"Multiple values for keychain item attribute");
   154             SecKeychainItemFreeAttributesAndData(list, NULL);
   155         }
   156     }
   157 #endif
   158     return value;
   159 }
   160 
   161 + (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   162     NSData *value = [self _getAttribute: attr ofItem: item];
   163     if (!value) return nil;
   164     const char *bytes = value.bytes;
   165     size_t length = value.length;
   166     if (length>0 && bytes[length-1] == 0)
   167         length--;           // Some values are null-terminated!?
   168     NSString *str = [[NSString alloc] initWithBytes: bytes length: length
   169                                            encoding: NSUTF8StringEncoding];
   170     if (!str)
   171         Warn(@"MYKeychainItem: Couldn't decode attr value as string");
   172     return [str autorelease];
   173 }
   174 
   175 - (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr {
   176     return [[self class] _getStringAttribute: attr ofItem: _itemRef];
   177 }
   178 
   179 
   180 + (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item
   181            stringValue: (NSString*)stringValue
   182 {
   183 #if MYCRYPTO_USE_IPHONE_API
   184     id value = stringValue ?(id)stringValue :(id)[NSNull null];
   185     NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey},
   186                                 {(id)kSecAttrKeyType, (id)attr},
   187                                 {(id)kSecMatchItemList, $array((id)item)});
   188     NSDictionary *attrs = $dict({(id)attr, value});
   189     return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate");
   190     
   191 #else
   192     NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding];
   193     SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes};
   194 	SecKeychainAttributeList list = {.count=1, .attr=&attribute};
   195     return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL),
   196                  @"SecKeychainItemModifyAttributesAndData");
   197 #endif
   198 }
   199 
   200 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   201     return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr];
   202 }
   203 
   204 
   205 @end
   206 
   207 
   208 
   209 
   210 BOOL check(OSStatus err, NSString *what) {
   211     if (err) {
   212 #if !MYCRYPTO_USE_IPHONE_API
   213         if (err < -2000000000)
   214             return checkcssm(err,what);
   215 #endif
   216         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err));
   217         if (err==-50)
   218             [NSException raise: NSGenericException format: @"%@ failed with paramErr (-50)",what];
   219         return NO;
   220     } else
   221         return YES;
   222 }
   223 
   224 #if !MYCRYPTO_USE_IPHONE_API
   225 BOOL checkcssm(CSSM_RETURN err, NSString *what) {
   226     if (err != CSSM_OK) {
   227         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err));
   228         return NO;
   229     } else
   230         return YES;
   231 }
   232 #endif
   233 
   234 
   235 
   236 /*
   237  Copyright (c) 2009, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   238  
   239  Redistribution and use in source and binary forms, with or without modification, are permitted
   240  provided that the following conditions are met:
   241  
   242  * Redistributions of source code must retain the above copyright notice, this list of conditions
   243  and the following disclaimer.
   244  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   245  and the following disclaimer in the documentation and/or other materials provided with the
   246  distribution.
   247  
   248  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   249  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   250  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   251  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   252  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   253   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   254  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   255  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   256  */