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