snej@0: // snej@0: // MYKeychainItem.m snej@0: // MYCrypto snej@0: // snej@0: // Created by Jens Alfke on 3/26/09. snej@0: // Copyright 2009 Jens Alfke. All rights reserved. snej@0: // snej@0: snej@0: #import "MYKeychainItem.h" snej@0: #import "MYCrypto_Private.h" snej@0: #import "MYErrorUtils.h" snej@0: snej@0: snej@0: NSString* const MYCSSMErrorDomain = @"CSSMErrorDomain"; snej@0: snej@0: snej@0: @implementation MYKeychainItem snej@0: snej@0: jens@23: - (id) initWithKeychainItemRef: (MYKeychainItemRef)itemRef snej@0: { snej@0: Assert(itemRef!=NULL); snej@0: self = [super init]; snej@0: if (self != nil) { snej@0: _itemRef = itemRef; snej@0: CFRetain(_itemRef); jens@23: LogTo(INIT,@"%@, _itemRef=%@", [self class], itemRef); snej@0: } snej@0: return self; snej@0: } snej@0: snej@0: snej@0: @synthesize keychainItemRef=_itemRef; snej@0: snej@0: - (void) dealloc snej@0: { snej@0: if (_itemRef) CFRelease(_itemRef); snej@0: [super dealloc]; snej@0: } snej@0: snej@2: - (void) finalize snej@2: { snej@2: if (_itemRef) CFRelease(_itemRef); snej@2: [super finalize]; snej@2: } snej@2: snej@0: - (id) copyWithZone: (NSZone*)zone { snej@0: // As keys are immutable, it's not necessary to make copies of them. This makes it more efficient snej@0: // to use instances as NSDictionary keys or store them in NSSets. snej@0: return [self retain]; snej@0: } snej@0: snej@0: - (BOOL) isEqual: (id)obj { snej@3: return (obj == self) || snej@3: ([obj isKindOfClass: [MYKeychainItem class]] && CFEqual(_itemRef, [obj keychainItemRef])); snej@0: } snej@0: snej@0: - (NSUInteger) hash { snej@0: return CFHash(_itemRef); snej@0: } snej@0: snej@1: - (NSString*) description { snej@1: return $sprintf(@"%@[%p]", [self class], _itemRef); //FIX: Can we do anything better? snej@1: } snej@1: snej@1: snej@0: - (NSArray*) _itemList { snej@0: return $array((id)_itemRef); snej@0: } snej@0: snej@0: snej@0: - (MYKeychain*) keychain { snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: return [MYKeychain defaultKeychain]; snej@0: #else snej@0: MYKeychain *keychain = nil; snej@0: SecKeychainRef keychainRef = NULL; snej@0: if (check(SecKeychainItemCopyKeychain((SecKeychainItemRef)_itemRef, &keychainRef), @"SecKeychainItemCopyKeychain")) { snej@0: if (keychainRef) { snej@0: keychain = [[[MYKeychain alloc] initWithKeychainRef: keychainRef] autorelease]; snej@0: CFRelease(keychainRef); snej@0: } snej@0: } snej@0: return keychain; snej@0: #endif snej@0: } snej@0: snej@0: - (BOOL) removeFromKeychain { snej@12: OSStatus err; snej@2: #if MYCRYPTO_USE_IPHONE_API jens@24: err = SecItemDelete((CFDictionaryRef) $dict( {(id)kSecValueRef, (id)_itemRef} )); snej@0: #else snej@12: err = SecKeychainItemDelete((SecKeychainItemRef)_itemRef); snej@14: if (err==errSecInvalidItemRef) snej@14: return YES; // result for an item that's not in a keychain snej@0: #endif snej@14: return err==errSecItemNotFound || check(err, @"SecKeychainItemDelete"); snej@0: } snej@0: snej@0: snej@0: #pragma mark - snej@0: #pragma mark DATA / METADATA ACCESSORS: snej@0: snej@0: snej@0: - (NSData*) _getContents: (OSStatus*)outError { snej@0: NSData *contents = nil; snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: #else snej@0: UInt32 length = 0; snej@0: void *bytes = NULL; snej@0: *outError = SecKeychainItemCopyAttributesAndData(_itemRef, NULL, NULL, NULL, &length, &bytes); snej@0: if (!*outError && bytes) { snej@0: contents = [NSData dataWithBytes: bytes length: length]; snej@0: SecKeychainItemFreeAttributesAndData(NULL, bytes); snej@0: } snej@0: #endif snej@0: return contents; snej@0: } snej@0: snej@0: + (NSData*) _getAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item { snej@0: NSData *value = nil; snej@2: #if MYCRYPTO_USE_IPHONE_API jens@24: NSDictionary *info = $dict( {(id)kSecValueRef, (id)item}, snej@0: {(id)kSecReturnAttributes, $true} ); snej@0: CFDictionaryRef attrs; snej@0: if (!check(SecItemCopyMatching((CFDictionaryRef)info, (CFTypeRef*)&attrs), @"SecItemCopyMatching")) snej@0: return nil; snej@0: CFTypeRef rawValue = CFDictionaryGetValue(attrs,attr); snej@0: value = rawValue ?[[(id)CFMakeCollectable(rawValue) retain] autorelease] :nil; snej@0: CFRelease(attrs); snej@0: snej@0: #else snej@0: UInt32 format = kSecFormatUnknown; snej@0: SecKeychainAttributeInfo info = {.count=1, .tag=(UInt32*)&attr, .format=&format}; snej@0: SecKeychainAttributeList *list = NULL; snej@0: snej@0: if (check(SecKeychainItemCopyAttributesAndData((SecKeychainItemRef)item, &info, snej@0: NULL, &list, NULL, NULL), snej@0: @"SecKeychainItemCopyAttributesAndData")) { snej@0: if (list) { snej@0: if (list->count == 1) snej@0: value = [NSData dataWithBytes: list->attr->data snej@0: length: list->attr->length]; snej@0: else if (list->count > 1) snej@0: Warn(@"Multiple values for keychain item attribute"); snej@0: SecKeychainItemFreeAttributesAndData(list, NULL); snej@0: } snej@0: } snej@0: #endif snej@0: return value; snej@0: } snej@0: snej@0: + (NSString*) _getStringAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item { snej@0: NSData *value = [self _getAttribute: attr ofItem: item]; snej@0: if (!value) return nil; snej@0: const char *bytes = value.bytes; snej@0: size_t length = value.length; snej@0: if (length>0 && bytes[length-1] == 0) snej@0: length--; // Some values are null-terminated!? snej@0: NSString *str = [[NSString alloc] initWithBytes: bytes length: length snej@0: encoding: NSUTF8StringEncoding]; snej@0: if (!str) snej@0: Warn(@"MYKeychainItem: Couldn't decode attr value as string"); snej@0: return [str autorelease]; snej@0: } snej@0: snej@0: - (NSString*) stringValueOfAttribute: (SecKeychainAttrType)attr { snej@0: return [[self class] _getStringAttribute: attr ofItem: _itemRef]; snej@0: } snej@0: snej@0: snej@0: + (BOOL) _setAttribute: (SecKeychainAttrType)attr ofItem: (MYKeychainItemRef)item snej@0: stringValue: (NSString*)stringValue snej@0: { snej@2: #if MYCRYPTO_USE_IPHONE_API snej@0: id value = stringValue ?(id)stringValue :(id)[NSNull null]; jens@24: NSDictionary *query = $dict({(id)kSecValueRef, (id)item}); snej@0: NSDictionary *attrs = $dict({(id)attr, value}); snej@0: return check(SecItemUpdate((CFDictionaryRef)query, (CFDictionaryRef)attrs), @"SecItemUpdate"); snej@0: snej@0: #else snej@0: NSData *data = [stringValue dataUsingEncoding: NSUTF8StringEncoding]; snej@0: SecKeychainAttribute attribute = {.tag=attr, .length=data.length, .data=(void*)data.bytes}; snej@0: SecKeychainAttributeList list = {.count=1, .attr=&attribute}; snej@0: return check(SecKeychainItemModifyAttributesAndData((SecKeychainItemRef)item, &list, 0, NULL), snej@0: @"SecKeychainItemModifyAttributesAndData"); snej@0: #endif snej@0: } snej@0: snej@0: - (BOOL) setValue: (NSString*)valueStr ofAttribute: (SecKeychainAttrType)attr { snej@0: return [[self class] _setAttribute: attr ofItem: _itemRef stringValue: valueStr]; snej@0: } snej@0: snej@0: snej@0: @end snej@0: snej@0: snej@0: snej@0: snej@0: BOOL check(OSStatus err, NSString *what) { snej@0: if (err) { snej@2: #if !MYCRYPTO_USE_IPHONE_API snej@0: if (err < -2000000000) snej@0: return checkcssm(err,what); snej@0: #endif snej@0: Warn(@"MYCrypto error, %@: %@", what, MYErrorName(NSOSStatusErrorDomain,err)); jens@23: if (err==-50) jens@23: [NSException raise: NSGenericException format: @"%@ failed with paramErr (-50)",what]; snej@0: return NO; snej@0: } else snej@0: return YES; snej@0: } snej@0: snej@2: #if !MYCRYPTO_USE_IPHONE_API snej@0: BOOL checkcssm(CSSM_RETURN err, NSString *what) { snej@0: if (err != CSSM_OK) { snej@0: Warn(@"MYCrypto error, %@: %@", what, MYErrorName(MYCSSMErrorDomain,err)); snej@0: return NO; snej@0: } else snej@0: return YES; snej@0: } snej@0: #endif snej@14: snej@14: snej@14: snej@14: /* snej@14: Copyright (c) 2009, Jens Alfke . All rights reserved. snej@14: snej@14: Redistribution and use in source and binary forms, with or without modification, are permitted snej@14: provided that the following conditions are met: snej@14: snej@14: * Redistributions of source code must retain the above copyright notice, this list of conditions snej@14: and the following disclaimer. snej@14: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions snej@14: and the following disclaimer in the documentation and/or other materials provided with the snej@14: distribution. snej@14: snej@14: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR snej@14: IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND snej@14: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI- snej@14: BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES snej@14: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR snej@14: PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN snej@14: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF snej@14: THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. snej@14: */