How to delete a certificate from KEYCHAIN using IOS keychain api

Dear All,


I have a requirement to delete the certificate associated with the service in the keychain. Below is the code I using to do that, however its not actually deleting the certificate.


Actually I have another mobile app that reads the available certificate for the service from device and displays to the user, since the certificate is not getting deleted by below code in previous "SSO App", the certificate list keeps growing in the "Services App"


(BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup

{


NSArray *items = [UICKeyChainStore itemsForService:service accessGroup:accessGroup];

for (NSDictionary *item in items) {

NSMutableDictionary *itemToDelete = [[NSMutableDictionary alloc] initWithDictionary:item];

[itemToDelete setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];

[itemToDelete setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

OSStatus status = SecItemDelete((__bridge CFDictionaryRef)itemToDelete);

if (status != errSecSuccess) {

return NO;

}

}

return YES;

}



Also please clarify me the below points if possible


1. Can a certificate stored in the service be deleted using IOS API code ?

2. If I remove a service from the keychain, does that remove the associated certificate as well or not ?

3. certificates can only be expired after its own expiration date ? and cannot be deleted using IOS APIs


Thanks,

Rajesh

I’m not sure what you’re asking here. Some things to consider:

  • Your question is about certificates but the code you posted tries to delete generic passwords (

    kSecClassGenericPassword
    ) or keys (
    kSecClassKey
    ) (although the way things are set up the former will override the latter).
  • You’re discussing multiple applications but you haven’t discussed how, if at all, keychain sharing is set up between those applications.

  • Are you talking about just a certificate or are you interested in a digital identity (that is, a certificate and a private key)? The latter would be used as a credential whereas the former would be used to check the credential of some other service. This distinction is important and is often a source of confusion.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi,


I am new to IOS & keychain APIs hence I couldn't come up with proper explanations, apologies for that! Also the code has been written by SAP team & we just noticed that reset certificate functionality is not working as expected. When we look at the code, we realized this is the piece of code that gets invoked when user resets certificate from the app, hence I posted the same. I don't even know how to validate this code & modify based on the certificate.


1. Yes, my question is all about certificates but not sure how to refer the same in " NSMutableDictionary *itemToDelete = [[NSMutableDictionary alloc] initWithDictionary:item];" like kSecClassKey or kSecClassGenericPassword.


2. At the moment I have source code of SSO app and not sure about the implementation in the other app, hence I am not fully aware of keychain relation & data sharing between the app


3. I am talking about certificate & private key and as you said its been used as validating user credentials for authentication.


Thanks,

Rajesh

Yes, my question is all about certificates but not sure how to refer [them] like kSecClassKey or kSecClassGenericPassword.

Two things:

  • That would be either

    kSecClassCertificate
    or
    kSecClassIdentity
    , depending on whether you are actually talking about certificates or digital identities.

    I’ll note that these values are in the header and documentation right next to the other values, so it's really not that hard to find them.

  • Apropos the certificate vs digital identity issue, the answer quoted above seems to contradict this answer:

    I am talking about certificate & private key and as you said its been used as validating user credentials for authentication.

    It's going to be hard to help here unless you can nail down some of the basics.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Hi,


I know I am not explaning the problem correctly, since I am very new to IOS. Better I will add some code snippets to add some clarity.


SSO App:

1. SSO app initially creates X509 certifcate, then convert the same to PKCS12 & adds to Keychain using SecItemAdd

2. Then creates SecIdentityRef object, then import PKCS12 data using SecPKCS12Import

3. Finally adds the SecIdentityRef to the keychain ( SecIdentityRef - is a requirement for internal prupose )


So the problem is SecIdentityRef (certificate) is not removed or deleted as expected.


Code creates SecRefObject & stores to keychain

   SecIdentityRef identityApp = nil;
    CFDataRef inPKCS12Data = (__bridge CFDataRef)signedDer;
    CFStringRef password = CFSTR("");
    const void *keys[] = { kSecImportExportPassphrase };
    const void *values[] = { password };
    CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    OSStatus securityError = SecPKCS12Import(inPKCS12Data, options, &items);
    CFRelease(options);
    CFRelease(password);
    if (securityError == errSecSuccess) {
        NSLog(@"Success opening p12 certificate. Items: %ld", CFArrayGetCount(items));
        CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
        identityApp = (SecIdentityRef)CFDictionaryGetValue(identityDict, kSecImportItemIdentity);
    } else {
        NSLog(@"Error opening Certificate.");
    }
 CFDataRef result;
NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)identityApp, kSecValueRef,
                                (id)kCFBooleanTrue, kSecReturnPersistentRef,nil];
    OSStatus itemAddStatus = SecItemAdd((__bridge CFDictionaryRef)attributes,(CFTypeRef *)&result);
     NSData *dataToStore = (__bridge_transfer NSData *)result;
     if (itemAddStatus != errSecSuccess)
    {
        NSLog(@"SecItemAdd did not succeed %d", (int)itemAddStatus);
    }else{
        NSLog(@"SecItemAdd was successful");
    }
    [UICKeyChainStore setData:dataToStore forKey:@"secIdentityRef" service:@"SSOService"];



Code to retirve items for service:


+ (NSArray *)itemsForService:(NSString *)service accessGroup:(NSString *)accessGroup
{
if (!service) {
        service = [self defaultService];
  }

NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
  [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
  [query setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];
  [query setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
  [query setObject:(__bridge id)kSecMatchLimitAll forKey:(__bridge id)kSecMatchLimit];
  [query setObject:service forKey:(__bridge id)kSecAttrService];
#if !TARGET_IPHONE_SIMULATOR && defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
    if (accessGroup) {
        [query setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
#endif

CFArrayRef result = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
if (status == errSecSuccess || status == errSecItemNotFound) {
  return CFBridgingRelease(result);
  } else {
  return nil;
  }
}



Function gets invoke when user resets/deletes certificate


(BOOL)removeAllItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup
{

NSArray *items = [UICKeyChainStore itemsForService:service accessGroup:accessGroup];
    for (NSDictionary *item in items) {
        NSMutableDictionary *itemToDelete = [[NSMutableDictionary alloc] initWithDictionary:item];
    
        [itemToDelete setObject:(__bridge id)kSecClassKey forKey:(__bridge id)kSecClass];
        [itemToDelete setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
        OSStatus status = SecItemDelete((__bridge CFDictionaryRef)itemToDelete);
    
        if (status != errSecSuccess) {
            return NO;
        }
    }
return YES;
}


Services App:

Services app reads the certificate created by SSO app & displays the same for user to pick before login, to authenticate

Services app supposed to display only one active certificate, but due to the above problem certificate list is keeps growing indefintely


Kindly suggest a work around to fix this, thanks a lot.


Rajesh

  1. SSO app initially creates X509 certifcate, then convert the same to PKCS12 & adds to Keychain using SecItemAdd

  2. Then creates SecIdentityRef object, then import PKCS12 data using SecPKCS12Import

  3. Finally adds the SecIdentityRef to the keychain ( SecIdentityRef - is a requirement for internal prupose )

OK, that all makes sense.

Moreover, the code you posted that calls

SecItemAdd
makes sense; I can’t guarantee it’s correct without look at it in great detail, but it certainly looks reasonable.

OTOH, the

SecItemCopyMatching
code makes no sense to me. If you’re putting a digital identity in the keychain (that is, a certificate and a private key that matches the public key from that certificate), why are you then trying to get it back as a generic password (
kSecClassGenericPassword
). So, either this code just doesn’t work or there’s some missing step here, something that involves generic passwords in addition to digital identities.

The code that calls

SecItemDelete
is also confusing. There’s one thing that’s definitely broken (you set
kSecClass
to
kSecClassKey
and then re-set it to
kSecClassGenericPassword
, so the first set is a no-op) but it’s also seems to suffer from the same confusion as the
SecItemCopyMatching
code.

Services app reads the certificate created by SSO app & displays the same for user to pick before login, to authenticate Services app supposed to display only one active certificate, but due to the above problem certificate list is keeps growing indefintely

Presumably you mean “digital identity” here; if your goal is to let the user choose an identity to authenticate as, you don’t want to show a list of certificates, you want to show a list of digital identities.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

SecItemCopyMatching retrives the list of key chain item that matches with the query & return as an array. Then the returned array is processed in removeAllItemsForService for deletion as shown in previous posts.


CFArrayRef result = nil;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
    if (status == errSecSuccess || status == errSecItemNotFound) {
        return CFBridgingRelease(result);
    } else {
        return nil;
    }




Below you can see the kSecClassGenericPassword implementation in the code


(BOOL)setData:(NSData *)data forKey:(NSString *)key service:(NSString *)service accessGroup:(NSString *)accessGroup
{
    if (!key) {
        return NO;
    }
    if (!service) {
        service = [self defaultService];
    }
    NSMutableDictionary *query = [[NSMutableDictionary alloc] init];
    [query setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [query setObject:service forKey:(__bridge id)kSecAttrService];
    [query setObject:key forKey:(__bridge id)kSecAttrGeneric];
    [query setObject:key forKey:(__bridge id)kSecAttrAccount];
#if !TARGET_IPHONE_SIMULATOR && defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
    if (accessGroup) {
        [query setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
#endif
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
    if (status == errSecSuccess) {
        if (data) {
            NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];
            [attributesToUpdate setObject:data forKey:(__bridge id)kSecValueData];
     
            status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)attributesToUpdate);
            if (status != errSecSuccess) {
                return NO;
            }
        } else {
            [self removeItemForKey:key service:service accessGroup:accessGroup];
        }
    } else if (status == errSecItemNotFound) {
        NSMutableDictionary *attributes = [[NSMutableDictionary alloc] init];
        [attributes setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
        [attributes setObject:service forKey:(__bridge id)kSecAttrService];
        [attributes setObject:key forKey:(__bridge id)kSecAttrGeneric];
        [attributes setObject:key forKey:(__bridge id)kSecAttrAccount];
#if TARGET_OS_IPHONE || (defined(MAC_OS_X_VERSION_10_9) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_9)
        [attributes setObject:(__bridge id)kSecAttrAccessibleAfterFirstUnlock forKey:(__bridge id)kSecAttrAccessible];
#endif
        [attributes setObject:data forKey:(__bridge id)kSecValueData];
#if !TARGET_IPHONE_SIMULATOR && defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
        if (accessGroup) {
            [attributes setObject:accessGroup forKey:(__bridge id)kSecAttrAccessGroup];
        }
#endif

        status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
        if (status != errSecSuccess) {
            return NO;
        }
    } else {
        return NO;
    }
    return YES;
}


Also could you please explain more on - displaying "list of digital identities" instead of certificates using key chain APIs. If possible please refer me some samples. Thank you.


Rajesh

How to delete a certificate from KEYCHAIN using IOS keychain api
 
 
Q