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