4 Replies
      Latest reply on May 16, 2018 5:46 AM by madsolar8582
      madsolar8582 Level 1 Level 1 (0 points)

        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.

        • Re: Proper way to update certificate in keychain
          eskimo Apple Staff Apple Staff (8,895 points)

          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"

            • Re: Proper way to update certificate in keychain
              madsolar8582 Level 1 Level 1 (0 points)

              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)?