Proper way to update certificate in keychain

I've been following the documentation in "Storing a Certificate in the Keychain" and I ran into an issue when calling SecItemUpdate, not SecItemAdd.


So, for the add, I create the query as follows and it succeeeds:

NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{
                                                                                  (__bridge NSString *)kSecClass : self.type,
                                                                                  (__bridge NSString *)kSecAttrLabel : self.identifier,
                                                                                  (__bridge NSString *)kSecAttrAccessible : self.accessibilityFlag,
                                                                                  (__bridge NSString *)kSecValueRef : (__bridge id)certificate
                                                                               }];
#if !(TARGET_IPHONE_SIMULATOR)
    if (self.accessGroup) {
        query[(__bridge NSString *)kSecAttrAccessGroup] = self.accessGroup;
    }
#endif


However, when I create the update query, it fails (-25303) when I use kSecValueRef:

NSMutableDictionary *query = [NSMutableDictionary dictionaryWithDictionary:@{
                                                                                  (__bridge NSString *)kSecClass : self.type,
                                                                                  (__bridge NSString *)kSecAttrLabel : self.identifier,
                                                                                  (__bridge NSString *)kSecAttrAccessible : self.accessibilityFlag
                                                                               }];
#if !(TARGET_IPHONE_SIMULATOR)
    if (self.accessGroup) {
        query[(__bridge NSString *)kSecAttrAccessGroup] = self.accessGroup;
    }
#endif

NSDictionary *changes = @{(__bridge NSString *)kSecValueRef : (__bridge id)certificate};


I can get the update operation to succeed if I change the update to be kSecValueData instead:

NSDictionary *changes = @{(__bridge NSString *)kSecValueData : (__bridge_transfer NSData *)SecCertificateCopyData(certificate)};


Is this the correct way of updating the certificate in the keychain? I ask since I would like to avoid the data conversion operation.

Accepted Reply

Thanks for all the backstory.

However, do you feel like this should be a valid use case (or if not, should there be documentation stating this)?

Clearly one or the other is true, so I definitely think that a bug report is in order. If this comes back as ‘behaves correctly’ you can then file another against the docs.

Please post your bug number, just for the record.

Share and Enjoy

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

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

Replies

I’m going to presume, based on the presence of the

TARGET_IPHONE_SIMULATOR
conditionals, that you’re working on an iOS-based platform. If that’s wrong, and this code is running on macOS, let me know because that can have a big impact on keychain code.

However, when I create the update query, it fails (-25303) when I use

kSecValueRef
:

Error -25303 is

errSecNoSuchAttr
, which is a little weird. To understand what’s going on here you have to understand that under the covers the keychain is a database where:
  • Each keychain item class (Internet password, generic password, certificate, key) is its own table.

  • Within a table, the columns represent the various keychain attributes.

  • And the rows represent keychain items of that class.

  • There is a uniqueness constraint across a set of columns to avoid duplicates (see the reference docs for

    errSecDuplicateItem
    for details).

So the fundamental keychain operations all work in terms of attributes, and when you pass in meta attributes, like

kSecValueRef
or
kSecValueData
, the keychain has to decompose the value you supply into attributes before it can perform the operation. In theory this should just work, but you seem to have stumbled across a case where it doesn’t.

Taking a step back, I’m not sure that

SecItemUpdate
operation is the correct operation for you. Certificates are fundamentally immutable. When you get a replacement certificate, it’s not an update of the old certificate but rather a new certificate that just happens to have the same subject, subject public key, and issuer. Notably the serial number is different, and that makes it a different certificate as far as the keychain is concerned (if you look at the doc I referenced above, you’ll see that
kSecAttrSerialNumber
is one of the attributes that uniquely identifies a certificate). So it might make sense to decompose this into two operations, a
SecItemAdd
and a
SecItemDelete
.

IMPORTANT This is very different from other keychain item classes, like a generic password, where the update operation is generally preferred over doing a delete then an add.

However, that approach only makes sense if you’re update the certificate itself. If you’re updating other attributes that don’t form part of the uniqueness constraint, like

kSecAttrLabel
, an update operation is the right thing to do.

So, what attributes are you updating here?

Share and Enjoy

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

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

Hi Quinn,


Yes, I am indeed working on some reusable code for iOS keychain management. I have certificates that are unique to each device so that they are identified via client authentication to the app's services. Since I'm making a generic class that abstracts away some of the keychain complexity, this is just something I ran into when I was creating the API wrappers. In actuality, I guess it doesn't necessarily make sense to "update" the certificate when it changes, rather, it makes more sense to replace it. The old way that we are storing certificates was to copy the DER representation of them and store that data as a generic password item. I'm rewriting this so that we use the correct SecCertificateRef APIs and the correct item class, but the old way does have the advantage of being able to update in-place (i.e. swap) the value of the same key whereas if I need to delete then add, I run the risk of the subsequent add operation failing and I've lost my original (fallback) data.


Regardless, since it appears that using SecValueRef isn't supported in this instance, I will proceed with your recommendation. However, do you feel like this should be a valid use case (or if not, should there be documentation stating this)?

Thanks for all the backstory.

However, do you feel like this should be a valid use case (or if not, should there be documentation stating this)?

Clearly one or the other is true, so I definitely think that a bug report is in order. If this comes back as ‘behaves correctly’ you can then file another against the docs.

Please post your bug number, just for the record.

Share and Enjoy

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

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

Sure thing! I've logged 40291705 for this.

To close the loop here, it was stated in my radar that this is intended behavior as kSecValueRef is considered a meta attribute as SecItemUpdate only allows "real keychain attributes" (kSecAttr constants).