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

5

11

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();
Buttonhook answered 12/11, 2014 at 9:23 Comment(3)
Is there any reason you're not using an API to do this work on a server?Bromide
@remus yes, the P12 file is created from the private key of the client and the certificate of the client, and the client shouldn't have to send their private key/public key out to the server if it's not necessary. It's not really private if it gets out of your hand just so you can use it, is it :)Buttonhook
'`However, to use this certificate in SSL requests, the private key and the X509Certificate have to be exported to a PKCS12 (P12) keystone.' That is not correct at all. You can generate the key pair using the Security framework and store it in the keychain/NSURLCredentialStorage. It is definitely NOT recommended to store private keys as files on the filesystem!Sanches
G
10

If you use openssl, you don't have to copy the full source code into your project, it is enough to add the libs and headers, so the openssl library can be used without any size problem. You can generate a key and a cert like that with openssl:

EVP_PKEY * pkey;
pkey = EVP_PKEY_new();

RSA * rsa;
rsa = RSA_generate_key(
        2048,   /* number of bits for the key - 2048 is a sensible value */
        RSA_F4, /* exponent - RSA_F4 is defined as 0x10001L */
        NULL,   /* callback - can be NULL if we aren't displaying progress */
        NULL    /* callback argument - not needed in this case */
);

EVP_PKEY_assign_RSA(pkey, rsa);

X509 * x509;
x509 = X509_new();

ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);

X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

X509_set_pubkey(x509, pkey);

X509_NAME * name;
name = X509_get_subject_name(x509);

X509_NAME_add_entry_by_txt(name, "C",  MBSTRING_ASC,
        (unsigned char *)"CA", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O",  MBSTRING_ASC,
        (unsigned char *)"MyCompany Inc.", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
        (unsigned char *)"localhost", -1, -1, 0);

X509_set_issuer_name(x509, name);

//X509_sign(x509, pkey, EVP_sha1());

const EVP_CIPHER *aConst = EVP_des_ede3_cbc();

And you can write this into pem format with these functions:

PEM_write_PrivateKey(f, pkey, NULL, NULL, 0, NULL, NULL);


PEM_write_X509(
            f,   /* write the certificate to the file we've opened */
            x509 /* our certificate */
);

After that it is possible to write these files into a p12 file, source from here: https://github.com/luvit/openssl/blob/master/openssl/demos/pkcs12/pkwrite.c

/* pkwrite.c */

#include <stdio.h>
#include <stdlib.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/pkcs12.h>

/* Simple PKCS#12 file creator */
int main(int argc, char **argv)
{
    FILE *fp;
    EVP_PKEY *pkey;
    X509 *cert;
    PKCS12 *p12;
    if (argc != 5) {
        fprintf(stderr, "Usage: pkwrite infile password name p12file\n");
        exit(1);
    }
    SSLeay_add_all_algorithms();
    ERR_load_crypto_strings();
    if (!(fp = fopen(argv[1], "r"))) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        exit(1);
    }
    cert = PEM_read_X509(fp, NULL, NULL, NULL);
    rewind(fp);
    pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
    fclose(fp);
    p12 = PKCS12_create(argv[2], argv[3], pkey, cert, NULL, 0,0,0,0,0);
    if(!p12) {
        fprintf(stderr, "Error creating PKCS#12 structure\n");
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    if (!(fp = fopen(argv[4], "wb"))) {
        fprintf(stderr, "Error opening file %s\n", argv[1]);
        ERR_print_errors_fp(stderr);
        exit(1);
    }
    i2d_PKCS12_fp(fp, p12);
    PKCS12_free(p12);
    fclose(fp);
    return 0;
}
Gaggle answered 19/11, 2014 at 9:9 Comment(4)
Thank you! You have saved the project :DButtonhook
so writing pem to disk with private key unprotected flushes security down the drain, right?Blotch
it was just a few PEM_write_PrivateKey parameters away :-) //X509_sign(x509, pkey, EVP_sha1()); in my case had to be uncommented for the p12 to be digestible on OSXBlotch
Do you have embedded the code in a swift project, if so you can share the code?Fiume
P
9

Thank you all very much for this nice solution!

I translated your code to Swift 3 and built the following function to create a P12 keystore using a signed X509 certificate and a RSA private key, both in PEM format:

func createP12(pemCertificate: String, pemPrivateKey: String) {
    // Read certificate
    let buffer = BIO_new(BIO_s_mem())
    pemCertificate.data(using: .utf8)!.withUnsafeBytes({ (bytes: UnsafePointer<Int8>) -> Void in
        BIO_puts(buffer, bytes)
    })
    let certificate = PEM_read_bio_X509(buffer, nil, nil, nil)
    X509_print_fp(stdout, certificate)
    // Read private key
    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)
    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()
}

EDIT:

OpenSSL can be used in iOS with the OpenSSL-for-iPhone project:

  1. Check out the repository
  2. Build the static libraries with ./build-libssl.sh
  3. Add $(YOUR_PATH)/OpenSSL-for-iPhone/include to header search paths
  4. Add $(YOUR_PATH)/OpenSSL-for-iPhone/lib to library search paths
  5. Add libcrypto.a and libssl.a to linked frameworks and libraries
  6. Add the following headers to the bridging header:

Project-Bridging-Header.h:

#import <openssl/err.h>
#import <openssl/pem.h>
#import <openssl/pkcs12.h>
#import <openssl/x509.h>
Paludal answered 25/1, 2017 at 15:59 Comment(8)
What library do we have to import in order to use this code? Do we have to bridge OpenSSL library from Object-C to swift?Cullis
You need to bridge the original OpenSSL library. The library can be built with OpenSSL-for-iPhone. I added a short explanation to my answer.Paludal
@Paludal How can I use it in private pod?Extremely
Sorry, I do not use any pods, so I do not know how to configure them.Paludal
I ran into some issues with this code in iOS 11 / XCode 9, the call to BIO_puts() results in a heap overflow when address sanitizers are enabled. See my answer below.Uropygium
Thank you for the extension. I was not aware of this problem.Paludal
// Set OpenSSL parameters OPENSSL_add_all_algorithms_noconf() ERR_load_crypto_strings() Getting error while use the above functions. Its showing not in scope. Can anyone please help with latest snippet to use for swift 5?Anchor
Any idea on how to add a root CA certificate in the mix? I'm loading a root certificate with PEM_read_X509(), and using it in PKCS12_create() replacing the nil argument in your code, but I get a EXC_BAD_ACCESS.Alectryomancy
U
3

My solution is similar to sundance's, I reworked it to get around address sanitizer / heap overflow issues encountered in XCode 9.

func createP12(secCertificate: SecCertificate, secPrivateKeyBase64: String, p12FileName: String, _ p12Password: String = "") throws -> String {
    // 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
    #if DEBUG
        X509_print_fp(stdout, certificate)
    #endif

    let pemPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\n\(secPrivateKeyBase64)\n-----END RSA PRIVATE KEY-----\n"

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

        // Print private key
        #if DEBUG
            PEM_write_PrivateKey(stdout, privateKey, nil, nil, 0, nil, nil)
        #endif

        // 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_noconf()
        ERR_load_crypto_strings()

        // 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, nil, 0, 0, 0, 0, 0) else {
            ERR_print_errors_fp(stderr)
            throw X509Error.cannotCreateP12Keystore
        }

        // Save P12 keystore
        let fileManager = FileManager.default
        let documentsPathURL = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
        let path = documentsPathURL.appendingPathComponent(p12FileName).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()

        NSLog("Wrote P12 keystore to: \(path)")
        return path
    })

    return p12Path
}

enum X509Error: Error {
    case privateKeyDoesNotMatchCertificate
    case cannotCreateP12Keystore
    case cannotOpenFileHandles
}
Uropygium answered 31/10, 2017 at 20:11 Comment(2)
Any idea on how to add a root CA certificate in the mix? I'm loading a root certificate with PEM_read_X509(), and using it in PKCS12_create() replacing the nil argument in your code, but I get a EXC_BAD_ACCESS.Alectryomancy
Sorry - not working in iOS any more these days I'm doing Android/Kotlin now. Glad you were able to find a solution.Uropygium
A
3

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
}
Alectryomancy answered 23/2, 2022 at 15:45 Comment(2)
which url i provide in certificateAuthorityFileURL?Rhineland
@Rhineland As the name implies, this is an URL pointing to a file (the certificate) in your bundle.Alectryomancy
A
2

Problem solved! Thank you, guys.

The p12 file now is created correctly.

The code now is:

NSString *certPem = [certificate pemCertificate];
[certPem writeToFile:[self certFilePath] atomically:YES encoding:NSUTF8StringEncoding error:nil];

const char *cert_chars = [certPem cStringUsingEncoding:NSUTF8StringEncoding];

BIO *buffer = BIO_new(BIO_s_mem());
BIO_puts(buffer, cert_chars);

X509 *cert;
cert = PEM_read_bio_X509(buffer, NULL, 0, NULL);
if (cert == NULL) {
    NSLog(@"error");
}
X509_print_fp(stdout, cert);

if (!X509_check_private_key(cert, [certificate privateKey])) {
    NSLog(@"PK error");
}

PKCS12 *p12;

SSLeay_add_all_algorithms();
ERR_load_crypto_strings();


p12 = PKCS12_create("passPhrase", "iOSMobileCertificate", [certificate privateKey], cert, NULL, 0,0,0,0,0);
if(!p12) {
    fprintf(stderr, "Error creating PKCS#12 structure\n");
    ERR_print_errors_fp(stderr);
    exit(1);
}

[self saveP12File:p12];

saveP12File is:

//create empty file
NSString *p12FilePath = [self p12FilePath];
if (![[NSFileManager defaultManager] createFileAtPath:p12FilePath contents:nil attributes:nil])
{
    NSLog(@"Error creating file for P12");
    @throw [[NSException alloc] initWithName:@"Fail getP12File" reason:@"Fail Error creating file for P12" userInfo:nil];
}

//get a FILE struct for the P12 file
NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:p12FilePath];
FILE *p12File = fdopen([outputFileHandle fileDescriptor], "w");

i2d_PKCS12_fp(p12File, p12);
PKCS12_free(p12);
fclose(p12File);

And p12FilePath is:

NSString *documentsFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
return [documentsFolder stringByAppendingPathComponent:@"CERT.p12"];

Thank you!

Anthozoan answered 19/11, 2014 at 18:5 Comment(5)
Anyone know what type "certificate" is?Violoncellist
@Violoncellist I would assume it is X509.Buttonhook
I am getting null for EVP_PKEY as I am converting Private key data into base64 string and then using following code to convert to EVP_PKEYGus
NSString *privateKey = [privateKeyData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; NSString *pemPrivateKey = [NSString stringWithFormat:@"-----BEGIN RSA PRIVATE KEY-----\n\%@\n-----END RSA PRIVATE KEY-----\n",privateKey]; BIO *publicBIO = NULL; EVP_PKEY *pkey = NULL; NSData *privateKeyData = [pemPrivateKey dataUsingEncoding:NSUTF8StringEncoding];Gus
if ((publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length])) == NO) { NSLog(@"BIO_new_mem_buf() failed!"); } pkey = PEM_read_bio_PrivateKey(publicBIO, NULL, NULL, NULL); //Here pkey is getting nullGus

© 2022 - 2024 — McMap. All rights reserved.