Add item to a specific keychain

I need to create a keychain and store two items (certificates) in it.

It's ok to create the keychain:

let kcPath = "/my/keychain/path"
var kc: SecKeychain?
var status = SecKeychainCreate(kcPath, 0, "", false, nil, &kc)
if status == errSecDuplicateKeychain {
     status = SecKeychainOpen(kcPath, &kc)
}
let cert1 = ...
let cert2 = ...

However how can I add cert1 and cert2 to keychain kc, that I've created? The SecItemAdd function doesn't allow to specify the keychain that I want store to (?!)

Is there a way to do this with Keychain Services API? How can I do this?

Answered by DTS Engineer in 278782022

The

SecItemAdd
function doesn't allow to specify the keychain that I want store to (?!)

Yes it does. Check out the

kSecUseKeychain
key.

Share and Enjoy

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

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

The

SecItemAdd
function doesn't allow to specify the keychain that I want store to (?!)

Yes it does. Check out the

kSecUseKeychain
key.

Share and Enjoy

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

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

Oh, thanks! 🙂

I'm stuck in a problem. Before, I was created a keychain (path.keychain), then after your answer, I've deleted it, to try using kSecUseKeychain. But now I'm getting "errSecDuplicatedKeychain" (line 3 below). I've searched Finder and the file doesn't exist. What is happening?


I'm trying this:

let kcPath = "/my/keychain/path.keychain"
var kc: SecKeychain?
var status = SecKeychainCreate(kcPath, 0, "", false, nil, &kc)
if status == errSecDuplicateKeychain {
     status = SecKeychainOpen(kcPath, &kc)
}
let cert1 = ...
let cert2 = ...

try! storeInKC(kc, certificate: cert1)


storeInKC function:

func storeInKC(_ keychain: SecKeychain?, certificate: SecCertificate) throws {
     let commonName = ...
    
     var addquery: [String: Any] = [ kSecClass as String: kSecClassCertificate,
                                     kSecValueRef as String: certificate,
                                     kSecAttrLabel as String: commonName ]
     if keychain != nil {  // eventually I use storeInKC() to store certificate in default keychain
          addquery[kSecUseKeychain as String] = keychain
     }

     let status = SecItemAdd(addquerry as CFDictionary, nil)
     guard status == errSecSuccess else {
          throw Something.err
     }
}

then after your answer, I've deleted it

How did you delete it? Using the Finder? Or

SecKeychainDelete
? If it was the former, try the latter.

Share and Enjoy

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

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

The problem persists. The status code of SecKeychainDelete is -25295 (The specified keychain is not a valid keychain file). I found the keychain file and delete it using the Finder (ok, it works), then I've created the keychain again and finally the SecKeychainDelete fails (line 05 of the first snipped).


Remembering that I'm want to store two certificates in a specific keychain (a new keychain). If I've already done it, then on the second time, the keychain needs to be deleted and "recreated" (create a new keychain).


The documentation of function SecKeychainDelete (https://developer.apple.com/documentation/security/1395206-seckeychaindelete?language=objc) says: "The result code errSecInvalidKeychain is returned if the specified keychain is invalid or if the value of the keychainOrArray parameter is invalid or NULL."
I've confirmed that keychainOrArray parameter isnt NULL. What are the reasons for keychain or the value of keychainOrArray parameter to be invalid?

The problem persists.

You mean the problem with the specific keychain that you deleted using the Finder? Or the problem in general?

If you deleted the keychain using the Finder then the system is definitely going to be confused. You should be able to clear that by restarting your Mac. If that doesn’t work, check that the keychain didn’t make it in to your keychain search list (you can dump and modify those using the

list-keychains
subcommand of
security
).

If the problem persists in general (that is, if you start with a new fixed path and you still see the problem) then I’m not sure what’s going wrong in your specific situation. I just tried this here (using the code below) and it works as expected.

@IBAction func testAction(_ sender: Any) {
    let keychainPath = "/Users/quinn/test.keychain"
    let password = [UInt8]("Hello Cruel World!".data(using: .utf8)!)
    var tmp: SecKeychain? = nil
    let createErr = SecKeychainCreate(keychainPath, UInt32(password.count), password, false, nil, &tmp)
    guard createErr == errSecSuccess else {
        NSLog("SecKeychainCreate failed, error: %d", createErr)
        return
    }
    let keychain = tmp!

    let addErr = SecItemAdd([
        kSecClass: kSecClassGenericPassword,
        kSecAttrService: "TestService",
        kSecAttrAccount: "mrgumby",
        kSecUseKeychain: keychain,
        kSecValueData: "opendoor".data(using: .utf8)!
    ] as NSDictionary, nil)
    if addErr != errSecSuccess {
        NSLog("SecItemAdd failed, error: %d", addErr)
    }

    let deleteErr = SecKeychainDelete(keychain)
    guard deleteErr == errSecSuccess else {
        NSLog("SecKeychainDelete failed, error: %d", createErr)
        return
    }
    NSLog("success")
}

Share and Enjoy

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

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

Thanks, Quinn! Actually you already answered my first question ("Add item to a specific keychain").


Please, about the current state: in fact, my specific doubt is how I can delete a duplicated keychain.

Using your code, I'm trying this here (code below). Running at firt time, the keychain "test" is created

and the generic password is added to it (ok). At second time, I want the keychain to be deleted, but

what happens is that createErr = errSecDuplicateKeychain, and deleteErr is errSecInvalidKeychain

(lines 9 and 10 below are executed).

How can I delete a duplicated keychain? What mistakes am I making?

    let keychainPath = "/Users/quinn/test.keychain"
    let password = [UInt8]("Hello Cruel World!".data(using: .utf8)!)
    var tmp: SecKeychain? = nil
    let createErr = SecKeychainCreate(keychainPath, UInt32(password.count), password, false, nil, &tmp)
    guard createErr == errSecSuccess else {
        if createErr == errSecDuplicateKeychain {
            let deleteErr = SecKeychainDelete(tmp)
            guard deleteErr == errSecSuccess else {
                print("SecKeychainDelete failed, error: %d", createErr)
                return
            }
            print("Success!")
            return
        }
        print("SecKeychainCreate failed, error: %d", createErr)
        return
    }
    let keychain = tmp!

    let addErr = SecItemAdd([
        kSecClass: kSecClassGenericPassword,
        kSecAttrService: "TestService",
        kSecAttrAccount: "mrgumby",
        kSecUseKeychain: keychain,
        kSecValueData: "opendoor".data(using: .utf8)!
        ] as NSDictionary, nil)
    if addErr != errSecSuccess {
        print("SecItemAdd failed, error: %d", addErr)
    }
    print("success")

The problem here is that

SecKeychainCreate
only returns a keychain reference in
tmp
if it succeeds. So line 7 is never going to work because
tmp
isn’t a valid keychain.

Note I was surprised that this code type checks, because

tmp
is
SecKeychain?
(optional) and it would make sense for
SecKeychainDelete
to take a
SecKeychain
(non optional). However it seems that
SecKeychainDelete
actually takes an optional and then guarantees to return
errSecInvalidKeychain
if it’s nil. Weird.

The solution is to try to open the keychain (

SecKeychainOpen
) at the top of this routine. If that works then you have a valid
SecKeychain
that you can pass to
SecKeychainDelete
.

Share and Enjoy

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

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

I didn't know it, thank you for details.

The solution you proposed make sense, but I was trying and I couldn't make it work. The problem is that SecKeychainOpen results in errSecSuccess even when I ask for a keychain that does not exists. Searching in Apple developers foruns, I found that to check if keychain exists (exactly what I want to do), I should to use SecKeychainGetStatus. But (now trying with) this function always returns "no error" too, even when I ask for a invalid keychain path. Can you tell me what I'm missing here?

var tmp: SecKeychain?
let kcPath = "/Users/user/Desktop/test.keychain"  // has not been created yet
let password = [UInt8]("123456".data(using: .utf8)!)

var kcStatus = SecKeychainStatus()
let status = SecKeychainGetStatus(tmp, &kcStatus)
if (status != errSecNoSuchKeychain) {  // status is equals to errSecSuccess, then it returns true
     let deleteErr = SecKeychainDelete(tmp)
     guard deleteErr == errSecSuccess else {
         return deleteErr
     } 
}

Searching for SecKeychainGetStatus result codes, I don't identified which of them indicate that keychain exists, but I think that if it doesn't exists, then an error code would be returned.

I'm not a Swift programmer, so I have doubt whether the second argument of SecKeychainGetStatus should be initialized like above, because it is a non-optional.

Rather than go down the

SecKeychainGetStatus
path, why not just open it (
SecKeychainOpen
) and, if that works, delete it (
SecKeychainDelete
)? Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
Add item to a specific keychain
 
 
Q