2

I know there is a function called SecPKCS12Import that allows you to import data from a p12 file. However, I want to go the reverse route. I have a SecCertificateRef and a public/private SecKeyRef, which I want to use to create a P12 file. Does anyone know how to do this on iPhone?

Thanks

hockeybro
  • 981
  • 1
  • 13
  • 41

2 Answers2

2

Unfortunately, there CommonCrypto does not provide any means to export PKCS12 containers let alone any other export functionality (even though its OSX counterpart can do that). There are ways to extract the SecKeyRef raw data from the key chain but then you still need to write all the PKCS12 wrapping yourself.

We were facing a similar issue and went with OpenSSL.

Compiling OpenSSL for iOS

Integrating OpenSSL requires a bit of work as you need to compile and link the OpenSSL sources yourself. Fortunately, there are some build scripts available so you do not have to do that yourself, e.g, https://github.com/x2on/OpenSSL-for-iPhone . I suggest you use them as you need to patch some of the Makefiles which is a bit of a hazel. Those build scripts generate static linked libraries for both iOS and tvOS. You just need to link them against your project and set the Header and Library Search Path accordingly.

CocoaPods

You can also use the official OpenSSL CocoaPod . That saves you the trouble of configuring your project.

Exporting PKCS12

As you might know, OpenSSL is a C library. That means you might want to encapsulate all the C functions into a Objective-C or Swift wrapper. There are some open source wrappers that support im- and exporting PKCS12 containers but I have not found a single one with good documentation. You should be able to derive the relevant snippets from some of the sources though.

https://github.com/microsec/MscX509Common/blob/master/src/MscPKCS12.m

You can have a look at this example as well http://fm4dd.com/openssl/pkcs12test.htm .

Hope that helps!

Jens Meder
  • 4,237
  • 1
  • 25
  • 25
  • Also, if I want to send a certificate and private Key to a server, is there any other way to do it without using the OpenSSL library? – hockeybro Jul 12 '16 at 17:25
  • In theory, you can export the private and public keys as NSData from keychain but any additional formatting, e.g., PEM, DER, X.509 or PKCS12 you need to implement yourself. Unfortunately, we never got this to work in our app. If you are interested you can have a look at `SecItemCopyMatching`. Good luck! – Jens Meder Jul 12 '16 at 19:53
  • Would it be possible to get snippets of code to generate a PKCS12 from OpenSSL and just copy this in, instead of importing the whole library? – hockeybro Jul 12 '16 at 22:27
  • That should be possible even though you might end up tracing dependencies. We have tried to do that with the RSA module but in the end it was too much of hazel to identify all dependencies. – Jens Meder Jul 13 '16 at 07:47
  • Does using OpenSSL library impact app performance or increase your chance of getting rejected by Apple? Just trying to figure out why it is not recommended. – hockeybro Jul 13 '16 at 18:13
  • OpenSSL does not have any performance impact on your app and you won't get rejected by Apple. There are some inconveniences that basically caused Apple to drop official support. The most notable one: The API can change quite a bit from version to version. That is a major problem for a company that provides an operating system with security system libraries. Every update to those libraries might break a lot of apps until they adopt the new API. – Jens Meder Jul 13 '16 at 20:49
  • Ok, instead of writing this output to a file, can I convert it to PEM, or some other format so that it can be sent to a server? – hockeybro Jul 14 '16 at 00:19
  • IMO that should be possible but I haven't tried it myself. – Jens Meder Jul 14 '16 at 08:05
2

I agree that this task can only be performed using OpenSSL. It is a bit tricky to compile it for iOS but with OpenSSL-for-iPhone it is quite possible.

To solve the given task of creating a PKCS12 keystore from a SecCertificate and a SecKey with Swift 3 just add the static libraries libssl.aand libcrypto.a to your project and create the following bridging header:

#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>

To create the keystore the input data have to be converted to OpenSSL data structures, which requires some creativity. The SecCertificate can be converted directly to DER format and then read into a X509 structure. The SecKey is even worse to handle. The only possible solution to get the data of the key is to write it to the keychain and get the reference. From the reference we can get the base 64 encoded string, which then can be read into a EVP_PKEY structure. Now, we can create the keystore and save it to a file. To access the data in the keystore via iOS functions we must read the file via let data = FileManager.default.contents(atPath: path)! as NSData

The full solution is shown in the following:

func createP12(secCertificate: SecCertificate, secPrivateKey: SecKey) {
    // Read certificate
    // Convert sec certificate to DER certificate
    let derCertificate = SecCertificateCopyData(secCertificate)
    // Create strange pointer to read DER certificate with OpenSSL
    // data must be a two-dimensional array containing the pointer to the DER certificate as single element at position [0][0]
    let certificatePointer = CFDataGetBytePtr(derCertificate)
    let certificateLength = CFDataGetLength(derCertificate)
    let certificateData = UnsafeMutablePointer<UnsafePointer<UInt8>?>.allocate(capacity: 1)
    certificateData.pointee = certificatePointer
    // Read DER certificate
    let certificate = d2i_X509(nil, certificateData, certificateLength)
    // Print certificate
    X509_print_fp(stdout, certificate)
    // Read private key
    // Convert sec key to PEM key
    let tempTag = "bundle.temp"
    let tempAttributes = [
        kSecClass: kSecClassKey,
        kSecAttrApplicationTag: tempTag,
        kSecAttrKeyType: kSecAttrKeyTypeRSA,
        kSecValueRef: secPrivateKey,
        kSecReturnData: kCFBooleanTrue
        ] as NSDictionary
    var privateKeyRef: AnyObject?
    // Store private key in keychain
    SecItemDelete(tempAttributes)
    guard SecItemAdd(tempAttributes, &privateKeyRef) == noErr else {
        NSLog("Cannot store private key")
        return
    }
    // Get private key data
    guard let privateKeyData = privateKeyRef as? Data else {
        NSLog("Cannot get private key data")
        return
    }
    let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(privateKeyData.base64EncodedString())\n-----END RSA PRIVATE KEY-----\n"
    // Delete private key in keychain
    SecItemDelete(tempAttributes)
    let privateKeyBuffer = BIO_new(BIO_s_mem())
    pemPrivateKey.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
        BIO_puts(privateKeyBuffer, bytes)
    })
    let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
    // !!! Remove in production: Print private key
    PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
    // Check if private key matches certificate
    guard X509_check_private_key(certificate, privateKey) == 1 else {
        NSLog("Private key does not match certificate")
        return
    }
    // Set OpenSSL parameters
    OPENSSL_add_all_algorithms_noconf()
    ERR_load_crypto_strings()
    // Create P12 keystore
    let passPhrase = UnsafeMutablePointer(mutating: ("" as NSString).utf8String)
    let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)
    guard let p12 = PKCS12_create(passPhrase, name, privateKey, certificate, nil, 0, 0, 0, 0, 0) else {
        NSLog("Cannot create P12 keystore:")
        ERR_print_errors_fp(stderr)
        return
    }
    // Save P12 keystore
    let fileManager = FileManager.default
    let tempDirectory = NSTemporaryDirectory() as NSString
    let path = tempDirectory.appendingPathComponent("ssl.p12")
    fileManager.createFile(atPath: path, contents: nil, attributes: nil)
    guard let fileHandle = FileHandle(forWritingAtPath: path) else {
        NSLog("Cannot open file handle: \(path)")
        return
    }
    let p12File = fdopen(fileHandle.fileDescriptor, "w")
    i2d_PKCS12_fp(p12File, p12)
    fclose(p12File)
    fileHandle.closeFile()
}
sundance
  • 2,930
  • 1
  • 20
  • 25
  • Good stuff. For security reasons you might want to put a comment by the code that prints out the private key not to do that in a production app? – mbonness Oct 24 '17 at 19:14
  • 1
    Thank you. Yes, you are right, keys should not be printed in production. I added a comment to the corresponding line. – sundance Nov 06 '17 at 09:39
  • Thank you for the code. Can I install the p12 on iOS device? @sundance – Lachtan Mar 05 '21 at 15:20