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