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