generate SecIdentityRef with SecCertificateRef, private key

First of all, sorry for my english.

i want iOS mutual authentication with client certificate.

but when i make NSURLCredential for NSURLAuthenticationMethodClientCertificate,

i have to need SecIdentityRef. but i cannot obtain identity from anywhere..


1.

i created keypair with SecKeyGeneratePair (RSA, 2048). and stored into keychain.


2.

send publickey to server. and get PEM type certificate (base64) from server, correctly.


3.

i decoded it. and make certificate with SecCertificateCreateWithData,

store it keychain directly

NSDictionary* certDic = @{
                                  (__bridge id)kSecClass : (__bridge id)kSecClassCertificate,
                                  (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
                                  (__bridge id)kSecValueRef : (__bridge id)savedCertRef,
                                  (__bridge id)kSecAttrLabel : cert_tag,
                                  };

OSStatus status = SecItemAdd((__bridge CFDictionaryRef)certDic, NULL);

perfectly work until here.


4.

now my keychain stored "Private Key", "Public Key","Certificate" from server


5.

when i try to access server, server request client certificate.

so i have to send client certificate from my keychain

but i have only SecCertificateRef. at that time. there is no SecIdentityRef

//in NSURLAuthenticationMethodClientCertificate block...

SecCertificateRef certificate = [DeviceCertificateControl getDeviceCertRef];
SecIdentityRef identity = ??????????????????;
           
NSArray *certArray = [NSArray arrayWithObject:(__bridge id)certificate];
* credential = [NSURLCredential credentialWithIdentity:identity certificates:certArray persistence:NSURLCredentialPersistencePermanent];


:::::::::: question.

1. how to make SecIdentityRef with SecCertificateRef with private key ref?

(many other examples, they use P12 file (pkcs#12), but i don't use it)


2. can i make pkcs#12 type data directly in iOS device?

3. is there way to send certification without identity?

many thanks.

Answered by DTS Engineer in 202264022

I’m going to tackle your questions out of order:

3. is there way to send certification without identity?

This makes no sense. My TLS for App Developers post explains why.

2. can i make pkcs#12 type data directly in iOS device?

Not via any iOS APIs. A PKCS#12 is just a bag of bytes, so you could write or acquire code to create one, but it’s not a lot of fun. It would be easier to fix your identity code.

1. how to make SecIdentityRef with SecCertificateRef with private key ref?

You need to do three things:

  • Before adding the certificate to the keychain, delete the public key from the keychain. There are situations where the presence of the public key confuses the identity matching code (r. 15615260).

  • Identity matching is done via the public key hash, which should be stored in the

    kSecAttrLabel
    kSecAttrApplicationLabel
    attribute of the private key and the
    kSecAttrPublicKeyHash
    attribute of the certificate. After you’ve got both items in the keychain, get these attributes to confirm that they match up.
  • Once the above is sorted out, you can get the identity by calling

    SecItemCopyMatching
    looking for items where the
    kSecClass
    is
    kSecClassIdentity
    .

Share and Enjoy

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

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

I’m going to tackle your questions out of order:

3. is there way to send certification without identity?

This makes no sense. My TLS for App Developers post explains why.

2. can i make pkcs#12 type data directly in iOS device?

Not via any iOS APIs. A PKCS#12 is just a bag of bytes, so you could write or acquire code to create one, but it’s not a lot of fun. It would be easier to fix your identity code.

1. how to make SecIdentityRef with SecCertificateRef with private key ref?

You need to do three things:

  • Before adding the certificate to the keychain, delete the public key from the keychain. There are situations where the presence of the public key confuses the identity matching code (r. 15615260).

  • Identity matching is done via the public key hash, which should be stored in the

    kSecAttrLabel
    kSecAttrApplicationLabel
    attribute of the private key and the
    kSecAttrPublicKeyHash
    attribute of the certificate. After you’ve got both items in the keychain, get these attributes to confirm that they match up.
  • Once the above is sorted out, you can get the identity by calling

    SecItemCopyMatching
    looking for items where the
    kSecClass
    is
    kSecClassIdentity
    .

Share and Enjoy

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

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

@eskimo Thank you for your contribution. This information is very difficult to find.


I am in a similar predicament, in which I've created a key pair and CSR on my iOS app, sent the CSR to the server and received a PEM certificate back. So I have now at my disposal the Private Key, Public Key and the CA Signed Certificate.


How do I determine the public key hash to follow your instructions here? How do I determine the ThumbprintAlgorithm used in the certificate without access to SecCertificateCopyValues?


Any thoughts on why these functions are not included in the iOS API

How do I determine the public key hash to follow your instructions here?

You can actually the public key hash if necessary, but my experience is that it’s not necessary. If you add the certificate to the keychain as a ref (that is, using

kSecValueRef
rather than
kSecValueData
), the system should set that attribute for you, at which point you can call
SecItemCopyMatching
to get an identity object.

Share and Enjoy

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

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

Hi,


My start situation is pretty much simillar to original mo.o post which means:
1. Generate pair of keys
2. Create CSR using keys from step 1

3. Do receive PEM in response to CSR post

4. Decode it

5. Store in same keychain as keys are

6. Trying to get the identity


Thus I did follow @eskimo advices however at the end I still can not get the identity.


Please find my sample code below and I would be very garatefull for any advice what I am doing wrong here? Did I use kSecAttrApplicationLabel and kSecAttrPublicKeyHash correctly? Maybe I have implemented something else incorectly?????


Thanks in advance



let privKeyAttrApplicationLabel = "com.example.keys.privateKey.kSecAttrApplicationLabel".data(using: .utf8)!

let privKeyAttrApplicationTag = "com.example.keys.privateKey.kSecAttrApplicationTag".data(using: .utf8)!

let pubKeyAttrApplicationTag = "com.example.keys.publicKey.kSecAttrApplicationTag".data(using: .utf8)!


let keyPairAttributes: [String: Any] =

[kSecAttrKeyType as String: kSecAttrKeyTypeEC,

kSecAttrKeySizeInBits as String: 256,

kSecPrivateKeyAttrs as String:

[kSecAttrIsPermanent as String: true,

kSecAttrApplicationLabel as String: privKeyAttrApplicationLabel,

kSecAttrApplicationTag as String: privKeyAttrApplicationTag],

kSecPublicKeyAttrs as String:

[kSecAttrIsPermanent: true,

kSecAttrApplicationTag: pubKeyAttrApplicationTag]

]


var publicKeyRef: SecKey? = nil

var privateKeyRef: SecKey? = nil


let keyPairGenerationStatus = SecKeyGeneratePair(keyPairAttributes as CFDictionary, &publicKeyRef, &privateKeyRef)

guard keyPairGenerationStatus == errSecSuccess else { return }


// At this stage I create CSR using above pair of keys and do exchane it to PEM (X509) to finaly parse that to DER


let pemCert: String = "MIO...A=="

let pemCertData = NSData(base64Encoded: pemCert, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters)!


// Before adding the certificate to the keychain, delete the public key from the keychain. There are situations where the presence of the public key confuses the identity matching code.


let deletePublicKeyQuery: [String: Any] = [kSecClass as String: kSecClassKey,

kSecAttrKeyType as String: kSecAttrKeyTypeEC,

kSecAttrApplicationTag as String: pubKeyAttrApplicationTag,

kSecReturnRef as String: true]


let deletePublicKeyStatus = SecItemDelete(deletePublicKeyQuery as CFDictionary)

guard deletePublicKeyStatus == errSecSuccess else { return }


guard let cert = SecCertificateCreateWithData(kCFAllocatorDefault, pemCertData as CFData) else { return }


// Add cer to keychain


let addquery: [String: Any] = [kSecClass as String: kSecClassCertificate,

kSecValueRef as String: cert,

kSecAttrPublicKeyHash as String: privKeyAttrApplicationLabel,

kSecAttrLabel as String: "com.example.keys.mycert"]


let certAddStatus = SecItemAdd(addquery as CFDictionary, nil)

guard certAddStatus == errSecSuccess else { return }


// As I understand correctly at this moment Identity matching is done via the public key hash, which should be stored in the kSecAttrLabel kSecAttrApplicationLabel attribute of the private key and the kSecAttrPublicKeyHash attribute of the certificate, but is it??????? Maybe I do it wrong????


// This is final step where I am trying to get the Identity using kSecAttrApplicationTag same as I did use to generate privateKey


let getIdentityQuery: [String: Any] = [kSecClass as String: kSecClassIdentity,

kSecAttrApplicationTag as String: privKeyAttrApplicationTag]

var identityItem: CFTypeRef?

let status = SecItemCopyMatching(getIdentityQuery as CFDictionary, &identityItem)

guard status == errSecSuccess else { return }


// At the final line here status is equal 0, however identityItem is nil! Why? Any idea what might be wrong??????

For the keychain to ‘see’ an identity the

kSecAttrApplicationLabel
attribute of the private key and the
kSecAttrPublicKeyHash
attribute of the certificate must match. So, the standard debugging step here is to dump these attributes and see whether they do in fact match. Here’s what I recommend:
  1. Create a small test app to explore this situation.

  2. Write code to delete all keys and certificates from the keychain, so you start with a blank slate each time.

  3. Then add in your test code.

  4. Write code that dumps all the keys and certificates in the keychain, and specifically the above-mentioned attributes.

  5. Run your app.

  6. If things fail, check the output from step 4 to confirm that:

    • There is a single key, the private key, and a single certificate

    • The above-mentioned attributes have the same value

Share and Enjoy

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

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

hey Quinn,


I have a similar situation:

1. I receive a ca certificate, client certificate and a private key from our API as PEM Strings

2. Certificate and a private key are stored into the keychain

3. I need to pass this identity at a later stage to the secure transport as a MQTT networking library


I'm following your advice and I got so far that I can verify the `SecAttrApplicationLabel` of my private key is equal to the `kSecAttrPublicKeyHash`.


The problem is that when I query the keychain for an identity, i receive just `-25300`. Is there some manual way to combine them as an indentity?


My code is below:


// add the client certificate
guard let cert = SecCertificateCreateWithData(kCFAllocatorDefault, pemCertData as CFData) else {
      XCTAssert(false, "Failed creating a Ceriticate")
      return
}
let addCertQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,
                                   kSecValueRef as String: cert,
                                   kSecAttrLabel as String: label]
   
let certAddStatus = SecItemAdd(addCertQuery as CFDictionary, nil)

// add the private key
let addKeyQuery: [String: Any] = [
          kSecClass as String: kSecClassKey,
          kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
          kSecAttrIsPermanent as String: true,
          kSecValueData as String: pemKeyData,
          kSecAttrApplicationLabel as String: publicHash,
          kSecAttrApplicationTag as String: tag
    ]
   
let privKeyAddStatus = SecItemAdd(addKeyQuery as CFDictionary, nil)
//

// query for all avaiable identities
let getIdentityQuery: [String: Any] = [kSecClass as String: kSecClassIdentity,
                                           kSecReturnData  as String: kCFBooleanTrue,
                                           kSecReturnAttributes as String: kCFBooleanTrue,
                                           kSecReturnRef as String: kCFBooleanTrue,
                                           kSecMatchLimit as String: kSecMatchLimitAll
    ]
var identityItem: CFTypeRef?
let status = SecItemCopyMatching(getIdentityQuery as CFDictionary, &identityItem)
// the status returns -25300

There’s two things I’d change here:

  • When you add your private key to the keychain (lines 13 through 22), don’t try to set up all the attributes yourself. Rather, create the private key from

    pemKeyData
    yourself (
    SecKeyCreateWithData
    ) and then add that via reference, much like do with the certificate. That’ll make sure that the private key has all the necessary attributes set correctly.
  • When searching the identity, pass in just

    kSecReturnRef
    , not
    kSecReturnData
    and
    kSecReturnAttributes
    . In the long term
    kSecReturnAttributes
    should be OK (so you can add it back into the mix once you’ve got past this hurdle) but
    kSecReturnData
    doesn’t really make any sense when talking about a synthetic construct like a digital identity.

Share and Enjoy

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

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

Thank you, Quinn! It worked as described.



Initially, I assumed that the SecKeyCreateWithData also adds the certificate to the Keychain but after re-reading your answer I saw that I should add it manually.



For those who might need it later, here's an example:


func stripPemHeaders(_ pemString: String) -> String {
    var result = pemString
    //
    result = result.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----\n", with: "")
    result = result.replacingOccurrences(of: "\n-----END RSA PRIVATE KEY-----\n", with: "")
    //
    result = result.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----\n", with: "")
    result = result.replacingOccurrences(of: "\n-----END CERTIFICATE-----\n", with: "")
    return result
  }

func storePrivateKey(for id: String, privateKeyString: String) -> OSStatus? {
    // cleanup key string from PEM headers
    let derPrivateKeyString = stripPemHeaders(privateKeyString)
    // we need to convert to Data before intitializing
    guard let pemKeyData = Data(base64Encoded: derPrivateKeyString, options:NSData.Base64DecodingOptions.ignoreUnknownCharacters) else {
      print("Error: couldn't parse the privateKeyString, pls check if headers were removed: \(privateKeyString)")
      return nil
    }
    // create the SecKey before we can store it to the keychain
    let sizeInBits = pemKeyData.count * 8
    let tag = "\(id)-tag".data(using: .utf8)!
    let keyDict: [CFString: Any] = [
      kSecAttrKeyType: kSecAttrKeyTypeRSA,
      kSecAttrKeyClass: kSecAttrKeyClassPrivate,
      kSecAttrKeySizeInBits: NSNumber(value: sizeInBits),
      kSecReturnPersistentRef: true
    ]
    var error: Unmanaged<CFError>?
    guard let key = SecKeyCreateWithData(pemKeyData as CFData, keyDict as CFDictionary, &error) else {
      print("Failed creating a Certificate from data \(error.debugDescription)")
      return nil
    }
    /// add key reference to keychain
    let addKeyQuery: [String: Any] = [
      kSecClass as String: kSecClassKey,
      kSecAttrIsPermanent as String: true,
      kSecValueRef as String: key,
      kSecAttrApplicationTag as String: tag
    ]
  
    let privKeyAddStatus = SecItemAdd(addKeyQuery as CFDictionary, nil)
    return privKeyAddStatus
  }

Hi @eskimo,


Thank you very much for your tips it works for me like a charm!


However I am facing now new issue and can not find anywhere clear enough answer to my problem. So briefly to remaind what I am trying to achieve:


1. Generate pair of keys
2. Create CSR using keys from step 1

3. Do receive PEM in response to CSR post

4. Decode it

5. Store in same keychain as keys are

6. Create digital identity credentials and use for URLAuthenticationChallenge for NSURLAuthenticationMethodClientCertificate

7. When try to call final enpoint (https://192.168.***.YYY:8080/myrequest), veryfing serves with ca_self_signed_cert and use digital identity credentials from step 6 do receive error in URLSession dataTask completion handler as follows:


NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."

NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x1c0119740>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-1, NSErrorPeerCertificateChainKey=(...)

Also in debbuger do notice following message:
[BoringSSL] Function boringssl_context_parse_identity_certificate_chain: line 3781 The type of the leaf certificate is not that of `SecIdentityGetTypeID()`


I am able to create now SecIdentity with success and add to URLSessionDelegate challange, however do receive error and it looks like ATS blocks something.



FYI my url looks as follows and it is not a local one: https://192.168.***.YYY:8080/myrequest



func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {


if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate) {


let getCertQuery: [String: Any] = [

kSecClass as String : kSecClassCertificate,

kSecReturnData as String : kCFBooleanTrue,

kSecReturnAttributes as String : kCFBooleanTrue,

kSecReturnRef as String : kCFBooleanTrue

]

var certItem: CFTypeRef?

let certStatus = withUnsafeMutablePointer(to: &certItem) {

SecItemCopyMatching(getCertQuery as CFDictionary, UnsafeMutablePointer($0))

}

guard certStatus == errSecSuccess else { return }

let certificate = certItem as! SecCertificate



let getIdentityQuery: [String: Any] = [

kSecClass as String : kSecClassIdentity,

kSecReturnData as String : kCFBooleanTrue,

kSecReturnAttributes as String : kCFBooleanTrue,

kSecReturnRef as String : kCFBooleanTrue,

kSecMatchLimit as String : kSecMatchLimitAll

]

var identityItem: CFTypeRef?

let identityStatus = withUnsafeMutablePointer(to: &identityItem) {

SecItemCopyMatching(getIdentityQuery as CFDictionary, UnsafeMutablePointer($0))

}

guard identityStatus == errSecSuccess else { return }

let identity = identityItem as! SecIdentity


// Create digital identity credentials

let credentials = URLCredential(identity: identity, certificates: [certificate], persistence: .forSession)

return completionHandler(.useCredential, credentials)



} else if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {


var localTrust: SecTrust?

let policy = SecPolicyCreateBasicX509()

let serverTrust = challenge.protectionSpace.serverTrust!

for index in 0..<SecTrustGetCertificateCount(serverTrust) {

guard let cert = SecTrustGetCertificateAtIndex(serverTrust, index) else { break }

var servTrust: SecTrust?

SecTrustCreateWithCertificates(cert, policy, &servTrust)

let localServKey = SecTrustCopyPublicKey(servTrust!)!


let certificateData = NSData(contentsOfFile: Bundle.main.path(forResource: "ca_self_signed_cert", ofType: "der")!)

let localCertificate = SecCertificateCreateWithData(kCFAllocatorDefault, certificateData!)



if SecTrustCreateWithCertificates(localCertificate!, policy, &localTrust) == errSecSuccess {

let localPublicKey = SecTrustCopyPublicKey(localTrust!)!

if (localPublicKey as AnyObject).isEqual(localServKey as AnyObject) {

print("trusted")

let credential = URLCredential(trust: serverTrust)

return completionHandler(.useCredential, credential)

}

}

}

}

}


I would be very gratefull for any tips here


Cheers

Have you look at what you’re getting back from the keychain in the debugger? Specifically, I don’t think that

certificate
and
identity
are the right type. Setting
kSecReturnData
,
kSecReturnAttributes
and
kSecReturnRef
cause the keychain to return you a dictionary, and you’re treated that as a ref.

Share and Enjoy

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

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

Hey eskimo,


You are absoulty right, I did use dictionary instead of

certificate
and
identity
. Everything works perfect now!

Thanks a lot for your help!!!

Hi eskimo,


I read already all topics about it, but I still can’t got identity from keychain.

I compare kSecAttrApplicationLabel of private key and kSecAttrPublicKeyHash of certificates, it’s same (value something like this: <fd92f1ee a28525668 … 28698ec3>)


I using next pipeline:

  1. Generate pair of RSA keys
  2. Send public key to server and generate CSR, after signing it with private key on device we send it to server again for creating certificate
  3. Load certificate to keychain on device
  4. Tried to get identity


I know that I miss step with deletion of public key, but I tried to get it from keychain and I can’t do it (I think that it not stored there, I always take it from private key)


I using next code for creation keys, loading certificate and get’s Identity:


Creation key pairs:


let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,

kSecAttrAccessibleWhenUnlockedThisDeviceOnly,

.userPresence,

nil)! /

let attributes: [String: Any] =

[kSecAttrKeyType as String: kSecAttrKeyTypeRSA as String,

kSecAttrKeySizeInBits as String: keySize,

kSecPrivateKeyAttrs as String:

[kSecAttrIsPermanent as String: true,

kSecAttrApplicationTag as String: alias,

kSecAttrAccessControl as String: access]

]


var error: Unmanaged<CFError>?

privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error)


if privateKey == nil{

var errorText = error!.takeRetainedValue() as Error

throw KeyGeneratorError.privateKeyNotDefined

}


publicKey = SecKeyCopyPublicKey(privateKey!)


if let cfdata = SecKeyCopyExternalRepresentation(publicKey!, &error) {

let data:Data = cfdata as Data

let key = self.format(keyData: data, withPemType: "RSA PUBLIC KEY")

return key

}



Import certificate:


let derCert = self.convertToDerFrom(pem: pem)

let certificate = SecCertificateCreateWithData(kCFAllocatorDefault, derCert)

guard certificate != nil else {

throw KeychainError.certificateCreationFailed

}

let certificateQuery: [String: Any] = [kSecClass as String: kSecClassCertificate,

kSecValueRef as String: certificate!,

kSecAttrLabel as String: self.alias]

let result = SecItemAdd(certificateQuery as CFDictionary, nil)

guard result == errSecSuccess else {

throw KeychainError.unexpectedCertificateAddError

}



Getting list of identity (tried to get one or many, same result):


let identityQuery: [String: Any] = [kSecClass as String: kSecClassIdentity,

kSecMatchLimit as String: kSecMatchLimitAll,

kSecReturnRef as String: true]


var identityRef: CFTypeRef?


let result = SecItemCopyMatching(identityQuery as CFDictionary, &identityRef)


guard result == errSecSuccess else {

throw KeychainItem.KeychainError.unhandledError(status: result)

}



Mb I doing something wrong?

It’s hard to say what’s going on here without looking at it in a lot more detail, more detail than I have time for here on DevForums. My recommendation is that you open a DTS tech support incident and we can pick things up in that context.

Share and Enjoy

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

let myEmail = "eskimo" + "1" + "@apple.com"
Thanks @eskimo for your help! I managed to get it working because of your replies in this thread...

The only thing missing is how to add access control; I tried adding it while storing the private key:
Code Block
     let privateKeyAttributes: [String: Any] = [
      kSecAttrIsPermanent as String: true,
      kSecAttrApplicationLabel as String: RSA.privKeyAttrApplicationLabel,
      kSecAttrAccessControl as String: RSA.getBioSecAccessControl(),
      kSecUseAuthenticationContext as String: LAContext(),
      kSecAttrApplicationTag as String: RSA.privKeyAttrApplicationTag
    ]


But unfortunately this always gets me the osstatus 34018 when fetching via

Code Block
     let getIdentityQuery: [String: Any] = [kSecClass as String: kSecClassIdentity,
                        kSecReturnRef as String: true,
                        kSecAttrApplicationTag as String: privKeyAttrApplicationTag]


How can I protect the private key with biometrics? Thanks!
I’m going to recommend that you start a new thread for this. Feel free to reference this one for background. Also, tag with Security so that I see it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
generate SecIdentityRef with SecCertificateRef, private key
 
 
Q