MYKeychainItem.m
author snej@snej.local
Sat Apr 18 18:12:06 2009 -0700 (2009-04-18)
changeset 12 e4c971be4079
parent 3 1dfe820d7ebe
child 13 6fd9177eb6da
permissions -rw-r--r--
Working on export/import of symmetric keys, and passphrase entry. Not ready for release quite yet.
     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 - (void) finalize
    41 {
    42     if (_itemRef) CFRelease(_itemRef);
    43     [super finalize];
    44 }
    45 
    46 - (id) copyWithZone: (NSZone*)zone {
    47     // As keys are immutable, it's not necessary to make copies of them. This makes it more efficient
    48     // to use instances as NSDictionary keys or store them in NSSets.
    49     return [self retain];
    50 }
    51 
    52 - (BOOL) isEqual: (id)obj {
    53     return (obj == self) ||
    54            ([obj isKindOfClass: [MYKeychainItem class]] && CFEqual(_itemRef, [obj keychainItemRef]));
    55 }
    56 
    57 - (NSUInteger) hash {
    58     return CFHash(_itemRef);
    59 }
    60 
    61 - (NSString*) description {
    62     return $sprintf(@"%@[%p]", [self class], _itemRef);     //FIX: Can we do anything better?
    63 }
    64 
    65 
    66 - (NSArray*) _itemList {
    67     return $array((id)_itemRef);
    68 }
    69 
    70 #if MYCRYPTO_USE_IPHONE_API
    71 - (CFDictionaryRef) asQuery {
    72     return (CFDictionaryRef) $dict( {(id)kSecClass, (id)kSecClassKey},//FIX
    73                                     {(id)kSecMatchItemList, self._itemList} );
    74 }
    75 #endif
    76 
    77 
    78 - (MYKeychain*) keychain {
    79 #if MYCRYPTO_USE_IPHONE_API
    80     return [MYKeychain defaultKeychain];
    81 #else
    82     MYKeychain *keychain = nil;
    83     SecKeychainRef keychainRef = NULL;
    84     if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) {
    85         if (keychainRef) {
    86             keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease];
    87             CFRelease(keychainRef);
    88         }
    89     }
    90     return keychain;
    91 #endif
    92 }
    93 
    94 - (BOOL) removeFromKeychain {
    95     OSStatus err;
    96 #if MYCRYPTO_USE_IPHONE_API
    97     err = SecItemDelete(self.asQuery);
    98 #else
    99     err = SecKeychainItemDelete((SecKeychainItemRef)_itemRef);
   100 #endif
   101     return err==errSecItemNotFound || check(err, @"SecKeychainItemDelete");
   102 }
   103 
   104 
   105 #pragma mark -
   106 #pragma mark DATA / METADATA ACCESSORS:
   107 
   108 
   109 - (NSData*) _getContents: (OSStatus*)outError {
   110     NSData *contents = nil;
   111 #if MYCRYPTO_USE_IPHONE_API
   112 #else
   113 	UInt32 length = 0;
   114     void *bytes = NULL;
   115     *outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes);
   116     if (!*outError && bytes) {
   117         contents = [NSData dataWithBytes: bytes length: length];
   118         SecKeychainItemFreeAttributesAndData(NULL, bytes);
   119     }
   120 #endif
   121     return contents;
   122 }
   123 
   124 + (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   125     NSData *value = nil;
   126 #if MYCRYPTO_USE_IPHONE_API
   127     NSDictionary *info = $dict( {(id)kSecClass, (id)kSecClassKey},
   128                                 {(id)kSecMatchItemList, $array((id)item)},
   129                                 {(id)kSecReturnAttributes, $true} );
   130     CFDictionaryRef attrs;
   131     if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching"))
   132         return nil;
   133     CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr);
   134     value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil;
   135     CFRelease(attrs);
   136     
   137 #else
   138 	UInt32 format = kSecFormatUnknown;
   139 	SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format};
   140     SecKeychainAttributeList *list = NULL;
   141 	
   142     if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info,
   143                                                    NULL, &list, NULL, NULL),
   144               @"SecKeychainItemCopyAttributesAndData")) {
   145         if (list) {
   146             if (list->count == 1)
   147                 value = [NSData dataWithBytes: list->attr->data
   148                                        length: list->attr->length];
   149             else if (list->count > 1)
   150                 Warn(@"Multiple values for keychain item attribute");
   151             SecKeychainItemFreeAttributesAndData(list, NULL);
   152         }
   153     }
   154 #endif
   155     return value;
   156 }
   157 
   158 + (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item {
   159     NSData *value = [self _getAttribute: attr ofItem: item];
   160     if (!value) return nil;
   161     const char *bytes = value.bytes;
   162     size_t length = value.length;
   163     if (length>0 && bytes[length-1] == 0)
   164         length--;           // Some values are null-terminated!?
   165     NSString *str = [[NSString alloc] initWithBytes: bytes length: length
   166                                            encoding: NSUTF8StringEncoding];
   167     if (!str)
   168         Warn(@"MYKeychainItem: Couldn't decode attr value as string");
   169     return [str autorelease];
   170 }
   171 
   172 - (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr {
   173     return [[self class] _getStringAttribute: attr ofItem: _itemRef];
   174 }
   175 
   176 
   177 + (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item
   178            stringValue: (NSString*)stringValue
   179 {
   180 #if MYCRYPTO_USE_IPHONE_API
   181     id value = stringValue ?(id)stringValue :(id)[NSNull null];
   182     NSDictionary *query = $dict({(id)kSecClass, (id)kSecClassKey},
   183                                 {(id)kSecAttrKeyType, (id)attr},
   184                                 {(id)kSecMatchItemList, $array((id)item)});
   185     NSDictionary *attrs = $dict({(id)attr, value});
   186     return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate");
   187     
   188 #else
   189     NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding];
   190     SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes};
   191 	SecKeychainAttributeList list = {.count=1, .attr=&attribute};
   192     return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL),
   193                  @"SecKeychainItemModifyAttributesAndData");
   194 #endif
   195 }
   196 
   197 - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr {
   198     return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr];
   199 }
   200 
   201 
   202 @end
   203 
   204 
   205 
   206 
   207 BOOL check(OSStatus err, NSString *what) {
   208     if (err) {
   209 #if !MYCRYPTO_USE_IPHONE_API
   210         if (err < -2000000000)
   211             return checkcssm(err,what);
   212 #endif
   213         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err));
   214         return NO;
   215     } else
   216         return YES;
   217 }
   218 
   219 #if !MYCRYPTO_USE_IPHONE_API
   220 BOOL checkcssm(CSSM_RETURN err, NSString *what) {
   221     if (err != CSSM_OK) {
   222         Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err));
   223         return NO;
   224     } else
   225         return YES;
   226 }
   227 #endif