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