Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS: How to create PKCS12 (P12) keystore from private key and x509certificate in application programmatically?

This question was apparently similar but had no answers of any kind: Programmatically create a x509 certificate for iPhone without using OpenSSL

In our application (server, client), we are implementing client authentication (SSL based on X509Certificate). We already have a way to generate a keypair, create a PKCS10 Certificate Signing Request, have this signed by the self-signed CA and create a X509Certificate, send this back. However, to use this certificate in SSL requests, the private key and the X509Certificate have to be exported to a PKCS12 (P12) keystore.

Does anyone know anything about how to do this, or even if it's possible? The client has to generate the P12 file (we don't want to give out the private key), and the client is running iOS, and is a mobile device. The solution worked for Android using BouncyCastle (SpongyCastle), but we found nothing for iOS.

EDIT: In Java, this export is done by the following:

    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    KeyStore ks = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
    ks.load(null);
    ks.setKeyEntry("key-alias", (Key) key, password.toCharArray(),
            new java.security.cert.Certificate[] { x509Certificate });
    ks.store(bos, password.toCharArray());
    bos.close();
    return bos.toByteArray();
like image 642
EpicPandaForce Avatar asked Nov 12 '14 09:11

EpicPandaForce


1 Answers

My solution is similar to mbonness', but reworked to suppress deprecation warning with Swift 5, and taking an optional rootCA certificate.

static func pkcs12(fromPem pemCertificate: String,
                   withPrivateKey pemPrivateKey: String,
                   p12Password: String = "",
                   certificateAuthorityFileURL: URL? = nil) throws -> NSData {
    // Create sec certificates from PEM string
    let modifiedCert = pemCertificate
        .replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
        .replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
        .replacingOccurrences(of: "\n", with: "")
        .trimmingCharacters(in: .whitespacesAndNewlines)

    guard let derCertificate = NSData(base64Encoded: modifiedCert, options: [])
    else {
        throw X509Error.cannotReadPEMCertificate
    }

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

    let p12Path = try pemPrivateKey.data(using: .utf8)!
        .withUnsafeBytes { bytes throws -> String in
            let privateKeyBuffer = BIO_new_mem_buf(bytes.baseAddress, Int32(pemPrivateKey.count))
            let privateKey = PEM_read_bio_PrivateKey(privateKeyBuffer, nil, nil, nil)
            defer {
                BIO_free(privateKeyBuffer)
            }

            // Check if private key matches certificate
            guard X509_check_private_key(certificate, privateKey) == 1 else {
                throw X509Error.privateKeyDoesNotMatchCertificate
            }

            // Set OpenSSL parameters
            OpenSSL_add_all_algorithms()
            ERR_load_CRYPTO_strings()

            // The CA cert needs to be in a stack of certs
            let certsStack = sk_X509_new_null()

            if let certificateAuthorityFileURL = certificateAuthorityFileURL {
                // Read root certiticate
                let rootCAFileHandle = try FileHandle(forReadingFrom: certificateAuthorityFileURL)
                let rootCAFile = fdopen(rootCAFileHandle.fileDescriptor, "r")
                let rootCA = PEM_read_X509(rootCAFile, nil, nil, nil)
                fclose(rootCAFile)
                rootCAFileHandle.closeFile()
                // Add certificate to the stack
                sk_X509_push(certsStack, rootCA)
            }

            // Create P12 keystore
            let passPhrase = UnsafeMutablePointer(mutating: (p12Password as NSString).utf8String)
            let name = UnsafeMutablePointer(mutating: ("SSL Certificate" as NSString).utf8String)

            guard let p12 = PKCS12_create(passPhrase,
                                          name,
                                          privateKey,
                                          certificate,
                                          certsStack,
                                          0,
                                          0,
                                          0,
                                          PKCS12_DEFAULT_ITER,
                                          0) else {
                ERR_print_errors_fp(stderr)
                throw X509Error.cannotCreateP12Keystore
            }

            // Save P12 keystore
            let fileManager = FileManager.default
            let path = fileManager
                .temporaryDirectory
                .appendingPathComponent(UUID().uuidString)
                .path
            fileManager.createFile(atPath: path, contents: nil, attributes: nil)
            guard let fileHandle = FileHandle(forWritingAtPath: path) else {
                NSLog("Cannot open file handle: \(path)")
                throw X509Error.cannotOpenFileHandles
            }
            let p12File = fdopen(fileHandle.fileDescriptor, "w")
            i2d_PKCS12_fp(p12File, p12)
            PKCS12_free(p12)
            fclose(p12File)
            fileHandle.closeFile()

            return path
        }

    // Read P12 Data
    guard let p12Data = NSData(contentsOfFile: p12Path) else {
        throw X509Error.cannotReadP12Certificate
    }

    // Remove temporary file
    try? FileManager.default.removeItem(atPath: p12Path)

    return p12Data
}
like image 50
jdanthinne Avatar answered Sep 19 '22 20:09

jdanthinne