Extracting PrivateKey from an APNS Push Certificate (p12)

Hi, my question is regarding APNs p12 files downloadable from Apple Dev Accounts.

I want to
  • download the push certificate for the server from Apple Dev Account

  • get the alias

  • get the certificate

  • get the private key

I send this info later to a server for enabling push notifications.

Issue:
I could make a function to get the SecIdentity from the p12 file, but when extracting the private key Data for converting later to String, I get an error with the passphrase.

The issue is only reproducible in macOS, as in iOS works fine.

Code Block
func buildAPNs(_ p12: NSData, _ password: String) throws -> ApplePushCertificate {
var alias : CFString? = nil
var cert: SecCertificate? = nil
var privateKey: SecKey? = nil
/* loadP12 does:
let keychain = try makeTemporaryKeyChain()
let passData = NSData(data: password.data(using: .utf8)!) as CFData
let options : NSDictionary = [key: passData, keychainKey: keychain!]
var items : CFArray? = NSArray() as CFArray
let status: OSStatus = SecPKCS12Import(p12, options, &items)
*/
let (securityError, rawItems, keychain) = try loadP12(p12: p12, password: password)
let items = rawItems! as! Array<Dictionary<String, Any>>
if securityError == errSecSuccess && items.count > 0 {
if let secIdentity = (items[0])[kSecImportItemIdentity as String] as! SecIdentity? {
let certSecurityError = SecIdentityCopyCertificate(secIdentity, &cert)
let aliasSecurityError = SecCertificateCopyCommonName(cert!, &alias)
let pkSecurityError = SecIdentityCopyPrivateKey(secIdentity, &privateKey)
guard pkSecurityError == errSecSuccess else {
throw NSError(domain: "Error with pk", code: Int(pkSecurityError), userInfo: nil)
}
guard certSecurityError == errSecSuccess else {
throw NSError(domain: "Error with certificate", code: Int(pkSecurityError), userInfo: nil)
}
guard aliasSecurityError == noErr else {
throw NSError(domain: "Error with alias", code: Int(pkSecurityError), userInfo: nil)
}
}
}
return ApplePushCertificate(alias: String(alias!),
certificate: cert!,
privateKey: privateKey!,
keychain: keychain)
}


with this ApplePushCertificate I call
Code Block
func getKeyData(apnsCert: ApplePushCertificate)-> String {
let keyData = SecKeyCopyExternalRepresentation(apnsCert.privateKey, &error)
//call (keyData as Data).base64EncodedString()
}


the error when trying to create keyData is:
Code Block
Error Domain=NSOSStatusErrorDomain Code=-25260 "MacOS error: -25260" UserInfo={NSDescription=MacOS error: -25260}


How can I solve this issue for MacOS?
I tried the same code (without creating a temporary keychain) in iOS and works fine without errors

Replies

This is one of those places where the macOS and iOS Security frameworks differ significantly. When you import a PKCS\#12 in this way, it ends up in the keychain. The private key’s keychain item is marked as “not extractable”, so you can’t extract its raw bits use SecKeyCopyExternalRepresentation.

It’s been a while since I messed around with this but you may be able to make progress using SecItemImport. This lets you control whether the item goes into the keychain kSecAttrIsPermanent and whether its raw data is extractable (kSecAttrIsExtractable).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"