Credentials.m

/*
    File:       Credentials.m
 
    Contains:   A model object for credentials.
 
    Written by: DTS
 
    Copyright:  Copyright (c) 2011 Apple Inc. All Rights Reserved.
 
    Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc.
                ("Apple") in consideration of your agreement to the following
                terms, and your use, installation, modification or
                redistribution of this Apple software constitutes acceptance of
                these terms.  If you do not agree with these terms, please do
                not use, install, modify or redistribute this Apple software.
 
                In consideration of your agreement to abide by the following
                terms, and subject to these terms, Apple grants you a personal,
                non-exclusive license, under Apple's copyrights in this
                original Apple software (the "Apple Software"), to use,
                reproduce, modify and redistribute the Apple Software, with or
                without modifications, in source and/or binary forms; provided
                that if you redistribute the Apple Software in its entirety and
                without modifications, you must retain this notice and the
                following text and disclaimers in all such redistributions of
                the Apple Software. Neither the name, trademarks, service marks
                or logos of Apple Inc. may be used to endorse or promote
                products derived from the Apple Software without specific prior
                written permission from Apple.  Except as expressly stated in
                this notice, no other rights or licenses, express or implied,
                are granted by Apple herein, including but not limited to any
                patent rights that may be infringed by your derivative works or
                by other works in which the Apple Software may be incorporated.
 
                The Apple Software is provided by Apple on an "AS IS" basis. 
                APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
                WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT,
                MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING
                THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
                COMBINATION WITH YOUR PRODUCTS.
 
                IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
                INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
                TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY
                OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION
                OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY
                OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
                OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF
                SUCH DAMAGE.
 
*/
 
#import "Credentials.h"
 
@interface Credentials ()
- (void)_refreshNotify:(BOOL)notify;
@end
 
@implementation Credentials
 
#pragma mark * Debugging
 
- (void)_printCertificate:(SecCertificateRef)certificate attributes:(NSDictionary *)attrs indent:(int)indent
    // Prints a certificate for debugging purposes.  The indent parameter is necessary to 
    // allow different indents depending on whether the key is part of an identity or not.
{
    CFStringRef         summary;
    NSString *          label;
    NSData *            hash;
 
    assert(certificate != NULL);
    assert(attrs != nil);
 
    summary = SecCertificateCopySubjectSummary(certificate);
    assert(summary != NULL);
    
    label = [attrs objectForKey:(id)kSecAttrLabel];
    if (label != nil) {
        fprintf(stderr, "%*slabel   = '%s'\n", indent, "", [label UTF8String]);
    }
    fprintf(stderr, "%*ssummary = '%s'\n", indent, "", [(NSString *)summary UTF8String]);
    hash = [attrs objectForKey:(id)kSecAttrPublicKeyHash];
    if (hash != nil) {
        fprintf(stderr, "%*shash    = %s\n", indent, "", [[hash description] UTF8String]);
    }
    
    CFRelease(summary);
}
 
- (void)_printKey:(SecKeyRef)key attributes:(NSDictionary *)attrs attrName:(CFTypeRef)attrName flagValues:(const char *)flagValues
    // Prints a flag within a key.
{
    #pragma unused(key)
    id  flag;
    
    assert(key != NULL);
    assert(attrs != nil);
    assert(attrName != NULL);
    assert(flagValues != NULL);
    assert(strlen(flagValues) == 2);
    
    flag = [attrs objectForKey:(id)attrName];
    if (flag == nil) {
        fprintf(stderr, "-");
    } else if ([flag boolValue]) {
        fprintf(stderr, "%c", flagValues[0]);
    } else {
        fprintf(stderr, "%c", flagValues[1]);
    }
}
 
- (void)_printKey:(SecKeyRef)key attributes:(NSDictionary *)attrs indent:(int)indent
    // Prints a key for debugging purposes.  The indent parameter is necessary to allow 
    // different indents depending on whether the key is part of an identity or not.
{
    #pragma unused(key)
    id          label;
    CFTypeRef   keyClass;
 
    assert(key != NULL);
    assert(attrs != nil);
 
    label = [attrs objectForKey:(id)kSecAttrLabel];
    if (label != nil) {
        fprintf(stderr, "%*slabel     = '%s'\n", indent, "", [label UTF8String]);
    }
    label = [attrs objectForKey:(id)kSecAttrApplicationLabel];
    if (label != nil) {
        fprintf(stderr, "%*sapp label = %s\n", indent, "", [[label description] UTF8String]);
    }
    label = [attrs objectForKey:(id)kSecAttrApplicationTag];
    if (label != nil) {
        fprintf(stderr, "%*sapp tag   = %s\n", indent, "", [[label description] UTF8String]);
    }
    fprintf(stderr, "%*sflags     = ", indent, "");
    [self _printKey:key attributes:attrs attrName:kSecAttrCanEncrypt flagValues:"Ee"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanDecrypt flagValues:"Dd"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanDerive  flagValues:"Rr"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanSign    flagValues:"Ss"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanVerify  flagValues:"Vv"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanWrap    flagValues:"Ww"];
    [self _printKey:key attributes:attrs attrName:kSecAttrCanUnwrap  flagValues:"Uu"];
    fprintf(stderr, "\n");
 
    keyClass = (CFTypeRef) [attrs objectForKey:(id)kSecAttrKeyClass];
    if (keyClass != nil) {
        const char *    keyClassStr;
        
        // keyClass is a CFNumber whereas kSecAttrKeyClassPublic (and so on)
        // are CFStrings.  Gosh, that makes things hard <rdar://problem/6914637>. 
        // So I compare their descriptions.  Yuck!
        
        if ( [[(id)keyClass description] isEqual:(id)kSecAttrKeyClassPublic] ) {
            keyClassStr = "kSecAttrKeyClassPublic";
        } else if ( [[(id)keyClass description] isEqual:(id)kSecAttrKeyClassPrivate] ) {
            keyClassStr = "kSecAttrKeyClassPrivate";
        } else if ( [[(id)keyClass description] isEqual:(id)kSecAttrKeyClassSymmetric] ) {
            keyClassStr = "kSecAttrKeyClassSymmetric";
        } else {
            keyClassStr = "?";
        }
        fprintf(stderr, "%*skey class = %s\n", indent, "", keyClassStr);
    }
}
 
- (void)_printIdentity:(SecIdentityRef)identity attributes:(NSDictionary *)attrs
    // Prints an identity for debugging purposes.
{
    OSStatus            err;
    SecCertificateRef   certificate;
    SecKeyRef           key;
 
    assert(identity != NULL);
    assert(attrs != nil);
    
    err = SecIdentityCopyCertificate(identity, &certificate);
    assert(err == noErr);
    
    err = SecIdentityCopyPrivateKey(identity, &key);
    assert(err == noErr);
    
    fprintf(stderr, "    certificate\n");
    [self _printCertificate:certificate attributes:attrs indent:6];
    fprintf(stderr, "    key\n");
    [self _printKey:key attributes:attrs indent:6];
    
    CFRelease(key);
    CFRelease(certificate);
}
 
- (void)_printCertificate:(SecCertificateRef)certificate attributes:(NSDictionary *)attrs
    // Prints a certificate for debugging purposes.  The real work is done 
    // by a helper routine that's shared with -_printIdentity:attributes:.
{
    assert(certificate != NULL);
    assert(attrs != nil);
    [self _printCertificate:certificate attributes:attrs indent:4];
}
 
- (void)_printKey:(SecKeyRef)key attributes:(NSDictionary *)attrs
    // Prints a certificate for debugging purposes.  The real work is done 
    // by a helper routine that's shared with -_printIdentity:attributes:.
{
    assert(key != NULL);
    assert(attrs != nil);
    [self _printKey:key attributes:attrs indent:4];
}
 
- (void)_printPassword:(id)ref attributes:(NSDictionary *)attrs
{
    #pragma unused(ref)
    NSString *  s;
    NSNumber *  n;
    NSData *    d;
    
    assert(ref == nil);         // there is no 'ref' object for Internet and generic passwords
    assert(attrs != nil);
 
/*
    common:
        kSecAttrDescription
        kSecAttrComment
        kSecAttrCreator
        kSecAttrType
        kSecAttrLabel
        kSecAttrIsInvisible
        kSecAttrIsNegative
        kSecAttrAccount
*/
 
    s = [attrs objectForKey:(id)kSecAttrDescription];
    if (s != nil) {
        fprintf(stderr, "    description = '%s'\n", [s UTF8String]);
    }
    s = [attrs objectForKey:(id)kSecAttrComment];
    if (s != nil) {
        fprintf(stderr, "    comment     = '%s'\n", [s UTF8String]);
    }
    s = [attrs objectForKey:(id)kSecAttrLabel];
    if (s != nil) {
        fprintf(stderr, "    label       = '%s'\n", [s UTF8String]);
    }
    s = [attrs objectForKey:(id)kSecAttrAccount];
    if (s != nil) {
        fprintf(stderr, "    account     = '%s'\n", [s UTF8String]);
    }
 
/*
    Internet:
        kSecAttrSecurityDomain
        kSecAttrServer
        kSecAttrProtocol
        kSecAttrAuthenticationType
        kSecAttrPort
        kSecAttrPath
*/
    s = [attrs objectForKey:(id)kSecAttrSecurityDomain];
    if (s != nil) {
        fprintf(stderr, "    domain      = '%s'\n", [s UTF8String]);
    }
    s = [attrs objectForKey:(id)kSecAttrServer];
    if (s != nil) {
        fprintf(stderr, "    server      = '%s'\n", [s UTF8String]);
    }
    s = [attrs objectForKey:(id)kSecAttrProtocol];
    if (s != nil) {
        static NSDictionary * sProtocolToNameMap;
        NSString *            protocolName;
        
        if (sProtocolToNameMap == nil) {
            sProtocolToNameMap = [[NSDictionary alloc] initWithObjectsAndKeys:
                @"FTP",         kSecAttrProtocolFTP, 
                @"FTP Account", kSecAttrProtocolFTPAccount, 
                @"HTTP",        kSecAttrProtocolHTTP, 
                @"IRC",         kSecAttrProtocolIRC, 
                @"NNTP",        kSecAttrProtocolNNTP, 
                @"POP3",        kSecAttrProtocolPOP3, 
                @"SMTP",        kSecAttrProtocolSMTP, 
                @"SOCKS",       kSecAttrProtocolSOCKS, 
                @"IMAP",        kSecAttrProtocolIMAP, 
                @"LDAP",        kSecAttrProtocolLDAP, 
                @"AppleTalk",   kSecAttrProtocolAppleTalk, 
                @"AFP",         kSecAttrProtocolAFP, 
                @"Telnet",      kSecAttrProtocolTelnet, 
                @"SSH",         kSecAttrProtocolSSH, 
                @"FTPS",        kSecAttrProtocolFTPS, 
                @"HTTPS",       kSecAttrProtocolHTTPS, 
                @"HTTP Proxy",  kSecAttrProtocolHTTPProxy, 
                @"HTTPS Proxy", kSecAttrProtocolHTTPSProxy, 
                @"FTP Proxy",   kSecAttrProtocolFTPProxy, 
                @"SMB",         kSecAttrProtocolSMB, 
                @"RTSP",        kSecAttrProtocolRTSP, 
                @"RTSP Proxy",  kSecAttrProtocolRTSPProxy, 
                @"DAAP",        kSecAttrProtocolDAAP, 
                @"EPPC",        kSecAttrProtocolEPPC, 
                @"IPP",         kSecAttrProtocolIPP, 
                @"NNTPS",       kSecAttrProtocolNNTPS, 
                @"LDAPS",       kSecAttrProtocolLDAPS, 
                @"TelnetS",     kSecAttrProtocolTelnetS, 
                @"IMAPS",       kSecAttrProtocolIMAPS, 
                @"IRCS",        kSecAttrProtocolIRCS, 
                @"POP3S",       kSecAttrProtocolPOP3S, 
                nil
            ];
            assert(sProtocolToNameMap != nil);
        }
        
        protocolName = [sProtocolToNameMap objectForKey:s];
        if (protocolName == nil) {
            protocolName = [NSString stringWithFormat:@"'%@'", s];
            assert(protocolName != nil);
        }
        fprintf(stderr, "    protocol    = '%s'\n", [protocolName UTF8String]);
    }
    n = [attrs objectForKey:(id)kSecAttrPort];
    if (n != nil) {
        fprintf(stderr, "    port        = %d\n", [n intValue]);
    }
    s = [attrs objectForKey:(id)kSecAttrPath];
    if (s != nil) {
        fprintf(stderr, "    path        = '%s'\n", [s UTF8String]);
    }
 
/*
    generic:
        kSecAttrService
        kSecAttrGeneric
*/
    s = [attrs objectForKey:(id)kSecAttrService];
    if (s != nil) {
        fprintf(stderr, "    service     = '%s'\n", [s UTF8String]);
    }
    d = [attrs objectForKey:(id)kSecAttrGeneric];
    if (d != nil) {
        fprintf(stderr, "    generic     = '%s'\n", [[d description] UTF8String]);
    }
}
 
- (void)_dumpCredentialsOfSecClass:(CFTypeRef)secClass printSelector:(SEL)printSelector
    // Iterates through all of the credentials of a particular class 
    // (identity, key, certificate, Internet, generic) and calls the selector on each.
{
    OSStatus    err;
    CFArrayRef  result;
    CFIndex     resultCount;
    CFIndex     resultIndex;
 
    assert(secClass != NULL);
    assert(printSelector != nil);
    
    result = NULL;
    err = SecItemCopyMatching(
        (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)secClass,           kSecClass, 
            kSecMatchLimitAll,      kSecMatchLimit, 
            kCFBooleanTrue,         kSecReturnRef, 
            kCFBooleanTrue,         kSecReturnAttributes, 
            nil
        ], 
        (CFTypeRef *) &result
    );
    assert( (err == noErr) == (result != NULL) );
    if (result != NULL) {
        assert( CFGetTypeID(result) == CFArrayGetTypeID() );
        
        resultCount = CFArrayGetCount(result);
        for (resultIndex = 0; resultIndex < resultCount; resultIndex++) {
            NSDictionary *  thisResult;
            
            fprintf(stderr, "  %zd\n", (ssize_t) resultIndex);
            thisResult = (NSDictionary *) CFArrayGetValueAtIndex(result, resultIndex);
            [self performSelector:printSelector withObject:[thisResult objectForKey:(NSString *)kSecValueRef] withObject:thisResult];
        }
        
        CFRelease(result);
    }
}
 
- (void)_dumpCredentials
{
    fprintf(stderr, "identities:\n");
    [self _dumpCredentialsOfSecClass:kSecClassIdentity printSelector:@selector(_printIdentity:attributes:)];
 
    fprintf(stderr, "certificates:\n");
    [self _dumpCredentialsOfSecClass:kSecClassCertificate printSelector:@selector(_printCertificate:attributes:)];
 
    fprintf(stderr, "keys:\n");
    [self _dumpCredentialsOfSecClass:kSecClassKey printSelector:@selector(_printKey:attributes:)];
 
    fprintf(stderr, "Internet:\n");
    [self _dumpCredentialsOfSecClass:kSecClassInternetPassword printSelector:@selector(_printPassword:attributes:)];
 
    fprintf(stderr, "generic:\n");
    [self _dumpCredentialsOfSecClass:kSecClassGenericPassword printSelector:@selector(_printPassword:attributes:)];
}
 
- (void)_resetCredentials
    // Deletes all credentials from the application's keychain.
{
    OSStatus    err;
    
    err = SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassIdentity,   kSecClass, 
            nil
        ]
    );
    assert(err == noErr);
    
    err = SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassCertificate, kSecClass, 
            nil
        ]
    );
    assert(err == noErr);
 
    err = SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassKey,         kSecClass, 
            nil
        ]
    );
    assert(err == noErr);
 
    err = SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassInternetPassword,  kSecClass, 
            nil
        ]
    );
    assert(err == noErr);
 
    err = SecItemDelete((CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,   kSecClass, 
            nil
        ]
    );
    assert(err == noErr);
}
 
#pragma mark * Core code
 
+ (Credentials *)sharedCredentials
{
    static Credentials * sSharedCredentials;
    
    if (sSharedCredentials == nil) {
        sSharedCredentials = [[Credentials alloc] init];
        assert(sSharedCredentials != nil);
    }
    return sSharedCredentials;
}
 
- (id)init
{
    self = [super init];
    if (self != nil) {
        self->_identities   = [[NSMutableArray alloc] init];
        assert(self->_identities != nil);
        self->_certificates = [[NSMutableArray alloc] init];
        assert(self->_certificates != nil);
        
        // Build initial values for the identities and certificates properties, 
        // without triggering KVO.
        
        [self _refreshNotify:NO];
    }
    return self;
}
 
- (NSArray *)_certificatesFromCertificates:(NSArray *)certificates excludingIdentities:(NSArray *)identities
    // Given an array of certificates and an array of identities, return all of the 
    // certificates that are /not/ associated with any identity.
{
    OSStatus                err;
    NSMutableArray *        standaloneCertificates;
    id                      obj;
    SecIdentityRef          identity;
    NSMutableDictionary *   certificateDataToIdentityMap;
    
    assert(certificates != nil);
    assert(identities != nil);
 
    // IMPORTANT: SecCertificateRef's are not uniqued (that is, you can get two 
    // different SecCertificateRef values that described the same fundamental 
    // certificate in the keychain), nor can they be compared with CFEqual.  So 
    // we match up certificates based on their data values.
    
    standaloneCertificates = [NSMutableArray array];
    assert(standaloneCertificates != NULL);
    
    // First build a map from certificate data values to corresponding identity object.
    
    certificateDataToIdentityMap = [NSMutableDictionary dictionary];
    assert(certificateDataToIdentityMap != NULL);
    
    for (obj in identities) {
        SecCertificateRef   identityCertificate;
        CFDataRef           identityCertificateData;
        
        identityCertificate = NULL;
        identityCertificateData = NULL;
        
        identity = (SecIdentityRef) obj;
        assert(CFGetTypeID(identity) == SecIdentityGetTypeID());
        
        err = SecIdentityCopyCertificate(identity, &identityCertificate);
        assert(err == noErr);
        
        identityCertificateData = SecCertificateCopyData(identityCertificate);
        assert(identityCertificateData != NULL);
        
        [certificateDataToIdentityMap setObject:(id)identity forKey:(NSData *)identityCertificateData];
        
        CFRelease(identityCertificateData);
        CFRelease(identityCertificate);
    }
    
    // Now go through the certificates looking for ones that aren't in that map. 
    // These are the standalone certificates.
    
    for (obj in certificates) {
        SecCertificateRef   certificate;
        CFDataRef           certificateData;
 
        certificate = (SecCertificateRef) obj;
        assert(CFGetTypeID(certificate) == SecCertificateGetTypeID());
 
        certificateData = SecCertificateCopyData(certificate);
        assert(certificateData != NULL);
 
        if ( [certificateDataToIdentityMap objectForKey:(NSData *)certificateData] == nil ) {
            [standaloneCertificates addObject:(id)certificate];
        }
 
        CFRelease(certificateData);
    }
    
    return standaloneCertificates;
}
 
static NSInteger OrderIdentities(id left, id right, void *context)
    // A sort function that compares two identities by the subject summary 
    // of their certificates.
{
    #pragma unused(context)
    NSInteger           result;
    OSStatus            err;
    SecIdentityRef      leftIdentity;
    SecIdentityRef      rightIdentity;
    SecCertificateRef   leftCertificate;
    SecCertificateRef   rightCertificate;
    CFStringRef         leftSubject;
    CFStringRef         rightSubject;
    
    leftIdentity  = (SecIdentityRef) left;
    rightIdentity = (SecIdentityRef) right;
    assert( (leftIdentity  != NULL) && (CFGetTypeID(leftIdentity ) == SecIdentityGetTypeID()));
    assert( (rightIdentity != NULL) && (CFGetTypeID(rightIdentity) == SecIdentityGetTypeID()));
    
    err = SecIdentityCopyCertificate(leftIdentity,  &leftCertificate);
    assert(err == noErr);
    err = SecIdentityCopyCertificate(rightIdentity, &rightCertificate);
    assert(err == noErr);
    
    leftSubject  = SecCertificateCopySubjectSummary(leftCertificate);
    rightSubject = SecCertificateCopySubjectSummary(rightCertificate);
    assert(leftSubject  != NULL);
    assert(rightSubject != NULL);
    
    result = [(NSString *)leftSubject localizedCaseInsensitiveCompare:(NSString *)rightSubject];
    
    CFRelease(leftSubject);
    CFRelease(rightSubject);
    CFRelease(leftCertificate);
    CFRelease(rightCertificate);
    
    return result;
}
 
static NSInteger OrderCertificates(id left, id right, void *context)
    // A sort function that compares two certificates by their subject summaries.
{
    #pragma unused(context)
    NSInteger           result;
    SecCertificateRef   leftCertificate;
    SecCertificateRef   rightCertificate;
    CFStringRef         leftSubject;
    CFStringRef         rightSubject;
    
    leftCertificate  = (SecCertificateRef) left;
    rightCertificate = (SecCertificateRef) right;
    assert( (leftCertificate  != NULL) && (CFGetTypeID(leftCertificate ) == SecCertificateGetTypeID()));
    assert( (rightCertificate != NULL) && (CFGetTypeID(rightCertificate) == SecCertificateGetTypeID()));
    
    leftSubject  = SecCertificateCopySubjectSummary(leftCertificate);
    rightSubject = SecCertificateCopySubjectSummary(rightCertificate);
    assert(leftSubject  != NULL);
    assert(rightSubject != NULL);
    
    result = [(NSString *)leftSubject localizedCaseInsensitiveCompare:(NSString *)rightSubject];
    
    CFRelease(leftSubject);
    CFRelease(rightSubject);
    
    return result;
}
 
- (void)_refreshNotify:(BOOL)notify
    // Refresh the identities and certificates properties from the current contents 
    // of the keychain, triggering a KVO notification if notify is YES.
    //
    // We make not attempt to rebuild the array nicely, that is, recording which 
    // values got inserted or removed.  This would be lots of tricky code, and it's 
    // not necessary given that our only client just reloads the entire table section 
    // when it gets the KVO notification.
{
    OSStatus        err;
    CFArrayRef      latestIdentities;
    CFArrayRef      latestCertificates;
 
    latestIdentities   = NULL;
    latestCertificates = NULL;
    
    // Get the current identities and certificates from the keychain.
    
    err = SecItemCopyMatching(
        (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
            (id) kSecClassIdentity,     kSecClass, 
            kSecMatchLimitAll,          kSecMatchLimit, 
            kCFBooleanTrue,             kSecReturnRef, 
            nil
        ],
        (CFTypeRef *) &latestIdentities
    );
    if (err == errSecItemNotFound) {
        latestIdentities = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
        assert(latestIdentities != NULL);
        err = noErr;
    }
    if (err == noErr) {
        err = SecItemCopyMatching(
            (CFDictionaryRef) [NSDictionary dictionaryWithObjectsAndKeys:
                (id) kSecClassCertificate,  kSecClass, 
                kSecMatchLimitAll,          kSecMatchLimit, 
                kCFBooleanTrue,             kSecReturnRef, 
                nil
            ],
            (CFTypeRef *) &latestCertificates
        );
    }
    if (err == errSecItemNotFound) {
        latestCertificates = CFArrayCreate(NULL, NULL, 0, &kCFTypeArrayCallBacks);
        assert(latestCertificates != NULL);
        err = noErr;
    }
    
    // Work out which certificates are standalone (that is, not part of any 
    // identity) and then update our identities and certificates properties.
    
    if (err == noErr) {
        NSArray *   standaloneCertificates;
        
        assert(latestIdentities != NULL);
        assert(CFGetTypeID(latestIdentities)   == CFArrayGetTypeID());
        assert(latestCertificates != NULL);
        assert(CFGetTypeID(latestCertificates) == CFArrayGetTypeID());
 
        // The processing here is /staggeringly/ inefficient.  There's lots I could 
        // do to to fix this (first up, avoid calling SecIdentityCopyCertificate multiple 
        // times for the same certificate by building a map of identities to certificates, 
        // and then, for each certificate, normalise and fold the subject summary strings 
        // to speed up comparisons), but for the moment the number of identities and 
        // certificates is tiny so it's not a big deal.  The following asserts will trip 
        // if the numbers get out of hand.
        
        assert(CFArrayGetCount(latestIdentities)   < 100);
        assert(CFArrayGetCount(latestCertificates) < 100);
 
        standaloneCertificates = [self _certificatesFromCertificates:(NSArray *)latestCertificates excludingIdentities:(NSArray *)latestIdentities];
        assert(standaloneCertificates != nil);
        
        if (notify) {
            [self willChangeValueForKey:@"identities"];
        }
        [self->_identities setArray:(NSArray *)latestIdentities];
        [self->_identities sortUsingFunction:OrderIdentities context:NULL];
        if (notify) {
            [self didChangeValueForKey:@"identities"];
        }
 
        if (notify) {
            [self willChangeValueForKey:@"certificates"];
        }
        [self->_certificates setArray:standaloneCertificates];
        [self->_certificates sortUsingFunction:OrderCertificates context:NULL];
        if (notify) {
            [self didChangeValueForKey:@"certificates"];
        }
    }
    assert(err == noErr);
    
    if (latestCertificates != NULL) {
        CFRelease(latestCertificates);
    }
    if (latestIdentities != NULL) {
        CFRelease(latestIdentities);
    }
}
 
- (NSArray *)identities
{
    return [[self->_identities copy] autorelease];
}
 
- (NSArray *)certificates
{
    return [[self->_certificates copy] autorelease];
}
 
- (void)refresh
{
    [self _refreshNotify:YES];
}
 
- (void)resetCredentials
{
    [self _resetCredentials];
    [self _refreshNotify:YES];
}
 
- (void)dumpCredentials
{
    [self _dumpCredentials];
}
 
@end